继承指使某个对象 关联 另一个对象的属性和方法. 大多数编程语言中运用的继承类型为基于类的继承(Classical Inheritance), 而JavaScript中的继承类型为基于原型的继承(Prototypal Inheritance).
原型 假设现在有一个对象obj, obj有prop1属性, 可以通过.
操作符获取这个属性值: obj.prop1
. 在JavaScript中, 所有对象(包括函数)都有一个隐藏的属性(无法直接获取的属性):原型属性.
原型属性的作用是关联到另一个对象proto
(The property is simply a reference to another object proto
). proto
还能关联另一个proto
. 形成了一个原型链(Prototype Chain)
如上图所示, 我们想通过obj.prop2
获取prop2
的值, 但obj
本身并没有这个属性, 那么就会通过原型链寻找proto
中是否有需要的属性.
如果有另一个对象obj2
, obj2
可以与obj1
共享同一个原型对象, 甚至同一条原型链, 如下图, 如果调用obj2.prop2
, 返回的是与obj1.prop2
同样的属性值, 指向的是内存空间的同一位置.
可以通过__proto__
为对象设置原型对象, 但在 实际应用中不应该这样做 ,对性能不利.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var person = { firstname : 'Default' , lastname : 'Default' , getFullName: function ( ) { return this .firstname + ' ' + this .lastname; } } var john = { firstname: 'John' , lastname: 'Doe' } john.__proto__ = person; console .log(john.getFullName()); var jane = { firstname: 'Jane' } jane.__proto__ = person; console .log(jane.getFullName());
一切皆对象 在JavaScript中, 一切皆对象(或原始类型) Everything is an object(or a primitive). 它们都有原型对象, 除了base object(对象的原型对象), 下例中的a.__proto__
.
1 2 3 4 5 6 7 8 9 10 var a = {};var b = function ( ) {};var c = [];console .log(a.__proto__); console .log(a.__proto__.__proto__); console .log(b.__proto__); console .log(b.__proto__.__proto__); console .log(c.__proto__); console .log(c.__proto__.__proto__);
对象的内省 Reflection: An object can look at itself, listing and changing its properties and methods.
Reflection: 在传统的面向类环境中,检查一个实例(JavaScript中的对象)的继承祖先(JavaScript中的委托关联)通常被称为内省(或者反射)。 – You Don’t Know JS
for-in: 以任意顺序迭代对象中的可枚举属性 .
for-in
与数组:
1 2 3 4 5 6 7 Array .prototype.myCustomFeature = "cool" ;var arr = ['John' , 'Jane' , 'Jim' ];for (var prop in arr){ console .log(prop + ': ' + arr[prop]); }
从上述代码看出, 数组的索引实际上是属性名, 但在原型对象中加入自己定义的属性之后, 用for-in
也迭代出了该属性, 因此不要在数组中使用for-in
, 应该用一般的for循环或forEach
.
hasOwnProperty: 用这个方法判断属性是否只存在于对象本身, 而不是在原型链的原型对象中.
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 30 31 32 33 var person = { firstname: 'Default' , lastname: 'Default' , getFullName: function ( ) { return this .firstname + ' ' + this .lastname; } } var john = { firstname: 'John' , lastname: 'Doe' } john.__proto__ = person; john.getFullName(); for (var prop in john){ console .log(prop + ': ' + john[prop]); } for (var prop in john){ if (john.hasOwnProperty(prop)){ console .log(prop + ': ' + john[prop]); } }
Underscore库中的extend
方法(运用了对象的内省), 见下例👇, 执行_.extend(john, jane, jim);
之后, john就有了jane和jim的属性, 除了exdend
方法, 还有extendOwn
方法:
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 30 31 32 33 34 var person = { firstname: 'Default' , lastname: 'Default' , getFullName: function ( ) { return this .firstname + ' ' + this .lastname; } } var john = { firstname: 'John' , lastname: 'Doe' } var jane = { address: '111 Main St.' , getFormalFullName: function ( ) { return this .lastname + ', ' + this .firstname; } } jane.__proto__ = person; var jim = { getFirstName: function ( ) { return firstname; } } _.extend(john, jane, jim); for (var prop in john){ console .log(prop + ': ' + john[prop]); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 firstname: Default lastname: Default address: 111 Main St. getFormalFullName: function ( ) { return this .lastname + ', ' + this .firstname; } getFullName: function ( ) { return this .firstname + ' ' + this .lastname; } getFirstName: function ( ) { return firstname; } firstname: John lastname: Doe address: 111 Main St. getFormalFullName: function ( ) { return this .lastname + ', ' + this .firstname; } getFirstName: function ( ) { return firstname; }
extend
方法中首个参数对象接受其后参数对象的属性, 包括后面的对象的原型属性, 而且如果有相同属性, 后面对象的属性会覆盖第一个参数对象属性.
extendOwn
与extend
相似, 但不包括后面对象的原型属性.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var createAssigner = function (keysFunc, undefinedOnly ) { return function (obj ) { var length = arguments .length; if (length < 2 || obj == null ) return obj; for (var index = 1 ; index < length; index++) { var source = arguments [index], keys = keysFunc(source), l = keys.length; for (var i = 0 ; i < l; i++) { var key = keys[i]; if (!undefinedOnly || obj[key] === void 0 ) obj[key] = source[key]; } } return obj; }; }; _.extend = createAssigner(_.allKeys); _.extendOwn = _.assign = createAssigner(_.keys);
创建对象 构造器函数与new操作符 1 2 3 4 5 6 function Person ( ) { this .firstname = 'John' ; this .lastname = 'Doe' ; } var john = new Person();console .log(john)
new
创建一个空对象
调用Person
函数, 新的执行环境被创建
将 this
指向所创建的空对象
Person
内定义的firstname和lastname成为新创建对象的属性
如果在构造器函数中没有声明返回的值, 返回的就是新创建的对象
1 2 3 4 5 6 7 8 9 function Person (firstname, lastname ) { this .firstname = firstname; this .lastname = lastname; } var john = new Person('John' , 'Doe' );console .log(john);var jane = new Person('Jane' , 'Doe' );console .log(jane);
利用构造器函数(Function Constructor)和new
创建对象时, 会给创建出的对象自动关联一个原型对象, 上述对象的原型对象是constructor为👇的Object{}
:1 2 3 4 function Person ( ) { this .firstname = 'John' ; this .lastname = 'Doe' ; }
__proto__
与prototype
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Person (firstname,lastname ) { this .firstname = firstname; this .lastname = lastname; } var john = new Person('John' ,'Doe' );var jane = new Person('Jane' ,'Doe' );console .log(john.__proto__ === Person.prototype); Person.prototype.greet = function ( ) { console .log('Hello, ' + this .firstname + ' ' + this .lastname); }; john.greet(); console .log(john.__proto__);
一般利用.prototype
给对象定义方法, 在构造器函数中定义属性, 其实也可以直接在构造器函数中定义方法, 但因为在JavaScript中, 函数是对象, 会占用内存空间, 见上例, 如果在构造器内定义greet
方法, 那么由这个构造器所创建出的每个对象中都含有greet
方法的拷贝, 这样就占用更多的内存空间. 而如果在.prototype
内定义方法, 不管创建出多少个对象, 这个方法在内存空间只存在一次.
内置构造器函数 1 2 3 4 5 6 var a = new Number (3 );console .log(a); var b = new String ('john' );console .log(b);
利用内置构造器函数创建出的变量a, b是形式为原始类型的对象, 不同的类型有不同的内置属性和方法, 也可以自己定义属性和方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 String .prototype.isLengthGreaterThan = function (limit ) { return this .length > limit; }; console .log('John' .isLengthGreaterThan(3 )); Number .prototype.isPositive = function ( ) { return this > 0 ; }; var a = Number (3 );console .log(a.isPositive()); var b = 3 ;console .log(a == b); console .log(a === b);
Object.create与原型继承 用构造器函数所实现的继承是模仿其他语言中的类式继承, 而用Object.create
实现的是真正的原型继承.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var person = { firstname: 'Default' , lastname: 'Default' , greet: function ( ) { return 'Hi ' + this .firstname + ' ' + this .lastname; } } var john = Object .create(person);console .log(john); john.firstname = 'John' ; john.lastname = 'Doe' ; console .log(john);
polyfill: 对于不支持某些特性的浏览器, 使用polyfill来实现同样的功能. (Code that adds a feature which the engine may lack.)
Object.create
的polyfill简化版:
1 2 3 4 5 6 7 8 9 10 if (!Object .create){ Object .create = function (o ) { if (arguments .length > 1 ){ throw new Error ('Object.create implementation only accepts the firstparameter' ); } function F ( ) {}; F.prototype = o; return new F(); }; }
参考