本文是阅读你不知道的JS(上)-原型部分所做的笔记。
原型与原型链 ECMA-262对于原型的定义(prototype):
object that provides shared properties for other objects
函数都有一个prototype
属性。
对象一般 都有一个[[prototype]]
属性,可以通过[[prototype]]
访问另一个对象,从而把对象和对象串联起来。
可通过new调用一个函数,使返回对象的[[prototype]]
和此函数的prototype
建立关联。
访问一个对象的属性/方法时,若未在此对象上找到对应的属性/方法,将会沿着原型链一直往上查找,即通过[[prototype]]
查找,直到查找到对应的属性/方法;反之最后会查找到null
,返回undefined
。
原型链之间的继承和传统面向类的继承不通,传统的类继承会进行复制,而原型链仅仅是关联 ,就像一个对象的行为委托在另一个对象上(圆形脸查找)。
创建对象关联 通过new关键字去调用一个函数fn,若无return语句或者return值不是一个对象或者函数,构造fn会返回一个新的对象,新对象的[[prototype]]
将会指向fn.prototype
;反之则正常return
。:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 let fn = function ( ) { this .name = 'oo' ; return 1 }; let o1 = new fn();o1.name === 'oo' o1.__proto__ === fn.prototype let fn = funciton(){ this .name = 'oo' ; return {} }; let o1 = new fn();o1.name o1.__proto__ === Object .prototype
此外:
1 2 let fn = function ( ) {};fn.prototype.constructor === fn.prototype;
函数原型的constructor默认指向函数本身,注意:这个constructor
属性可随意改写。
一个由如下方法创建的对象,其[[prototype]]
将会指向Object.prototype
:
1 2 3 4 5 6 7 8 9 10 11 let o1 = {};o1.__proto__ === Object .prototype let o1 = new Object ();o1.__proto__ === Object .prototype let o1 = Object ();o1.__proto__ === Object .prototype
如果想创建一个无属性、方法的空对象,可以用:
1 2 let o = Object .creat(null );o.hasOwnProperty === undefined
这种空对象不受原型链干扰,适合存储数据。
属性屏蔽 当去读一个属性时,会先查找对象本身是否有这个属性,若无,则会通过[[prototype]]
逐级向上依次查找,即原型链查找;反之,则取这个对象本身的属性。
看以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 let fn = function ( ) { }; fn.prototype.name = 'oo' ; let o1 = new fn();o1.__proto__ === fn.prototype; o1.__proto__.__proto__ === Object .prototype console .log(o1.name); o1.name = 'my oo' ; console .log(o1.name);
这种现象称为属性屏蔽。
属性赋值 为对象赋值其本身不具有而原型链上存在的属性,有三种情况。
该属性可写,则会为对象创建一个新属性。
该属性的[[Writable]]为false,赋值无效,不会为此对象创建一个新的属性。严格模式下还会报错。
该属性是一个访问器属性中的[[setter]],该[[setter]]一定会被调用,不会为此对象创建一个新的属性,也不会对该[[setter]]进行赋值。
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let fn = function ( ) { }; fn.prototype.name1 = 'o' ; let o1 = new fn();o1.name1 = 'my o' ; console .log(o1.name1); o1.hasOwnProperty('name1' ) Object .defineProperty(fn.prototype, 'name2' , { value: 'oo' }); o1.name2 = 'my oo' ; console .log(o1.name2);
应用原型链 原型链的思想看起来与类类似,通常使用new
的形式代表实例化一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Person (name ) { this .name = name; } function Student ( ) { Person.call(this , name); } Student.prototype = Object .create(Person.prototype); Object .setPrototypeOf(Student.prototype, Person.prototype ); Student.prototype instanceof Person Person.prototype.isPrototypeOf(Student.prototype) Object .getPrototypeOf(Student); let student = new Student('jack' );
类的继承实际上是复制 ,子类复制了父类的成员和方法。多态也不表示子类和父类有关系,然而JavaScript 并不会(像类那样)自动创建对象的副本。
然而,在你不知道的js上卷中,说了这种形式的危害(难以阅读和理解、难以维护),包括下列几种方式:
1.显式混入 1.1显式复制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function mixin ( sourceObj, targetObj ) { for (var key in sourceObj) { if (!(key in targetObj)) { targetObj[key] = sourceObj[key]; } } return targetObj; } var Vehicle = { engines: 1 , ignition: function ( ) { console .log( "Turning on my engine." ); }, drive: function ( ) { this .ignition(); console .log( "Steering and moving forward!" ); } }; var Car = mixin( Vehicle, { wheels: 4 , drive: function ( ) { Vehicle.drive.call( this ); console .log( "Rolling on all " + this .wheels + " wheels!" ); } } );
在mixin
函数中仅仅简单实现了属性的复制
,如果属性是基本类型值,那么是真正的复制,如果是引用类型值,那么复制
仅仅是改变了该属性的指向。
1.2寄生继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function Vehicle ( ) { this .engines = 1 ; } Vehicle.prototype.ignition = function ( ) { console .log( "Turning on my engine." ); }; Vehicle.prototype.drive = function ( ) { this .ignition(); console .log( "Steering and moving forward!" ); }; function Car ( ) { var car = new Vehicle(); car.wheels = 4 ; var vehDrive = car.drive; car.drive = function ( ) { vehDrive.call( this ); console .log( "Rolling on all " + this .wheels + " wheels!" ); return car; } var myCar = new Car();myCar.drive();
在Car
函数内部使用new
关键字创建了一个新的对象指向Vehicle.prototype
,之后返回了这个对象。
于是可以不用new
调用Car
。
2.隐式混入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var Something = { cool: function ( ) { this .greeting = "Hello World" ; this .count = this .count ? this .count + 1 : 1 ; } }; Something.cool(); Something.greeting; Something.count; var Another = { cool: function ( ) { Something.cool.call( this ); } }; Another.cool(); Another.greeting; Another.count;
对象和对象之间仅仅存在部分联系,在Another
的cool
方法中,调用了另一个对象的方法,可以看成是显式复制中的局部复制。
行为委托设计模式 @towrite
实现__proto__
参考
你不知道的JavaScript(上)