1、 什么是原型
每一个JavaScript对象(null除外)都和另一个对象相关联。’另一个’对象就是我们熟知的原型,每一个对象都从原型继承属性。
从《JavaScript权威指南》中可以知道,每一个对象都有一个原型对象,那么怎么知道一个对象所对应的原型对象呢? (ps:在es6中可以使用Object.getPrototypeOf()函数来插件到原型。)对象__proto__
属性的值就是它所对应的原型对象:
1 2 let arr = [] console.log(arr)
第二张图里面可以看到,不管我们给数组定义什么内容,它们总是有一些相同的方法和属性比如:map方法,length属性。
上图说明一个对象所拥有的属性不仅仅是它本身拥有的属性,它还会从其他对象中继承一些属性。当js在一个对象中找不到需要的属性时,它会到这个对象的父对象上去找,以此类推,这就构成了对象的原型链
。 简单来说,在 JavaScript 中每个对象都会有一个 __proto__
属性,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去 __proto__
里找这个属性,这个 __proto__
又会有自己的 __proto__
,于是就这样一直找下去。
1 2 3 4 5 6 7 8 9 let person = function ( ) {}; person.prototype .say = function ( ) { console .log ('hello world' ); }let littlePerson = new person (); littlePerson.say ();
2、什么是原型链 上文已经提到了原型链,接下来看看《JavaScript权威指南》里面关于原型链的解释:
所有的内置的构造函数(以及大部分的自定义构造函数)都具有一个继承自Object.prototype的原型。例如,Date.prototype的属性继承自Object.prototype,因此由new Date()创建的Date对象同时继承自Date.prototype和Object.prototype。这一系列链接的原型对象就是所谓的原型链(prototype chain)
2.1 继承
假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询属性x。如果原型对象中也没有x,但这个原型对象也有原型,那么继续在这个原型对象的原型上执行查询,直到找到x或者查找到一个原型是null的对象为止。可以看到,对象的原型属性构成了一个”链”,通过这个”链”可以实现属性的继承。
下面来一个继承的demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function inherit (p ) { if (p === null ) { throw TypeError (); } if (Object .create) { return Object .create(p); } if (typeof (p) !== 'object' && typeof (p) !== 'function' ) { throw TypeError (); } function foo ( ) {}; foo.prototype = p; return new foo(); }let obj = {}; obj.x = 1 ;let p = inherit(obj); p.y = 2 ;let q = inherit(p); console .log(q.x + q.y);
2.2 属性赋值
属性赋值操作首先检查原型链,以此判断是否允许赋值操作。例如,如果o继承自一个只读属性x,那么赋值操作是不允许的。 如果允许属性赋值操作,它也总是在原始对象上创建属性或对已有的属性赋值。而不会去修改原型链。 在JS中,只有在查询属性时才会体会到继承的存在,而设置属性则和继承无关。这是JS的一个重要特征,该特征让程序员可以有选择地覆盖继承的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function inherit (p) { if (p === null ) { throw TypeError(); // 因为原型链终点为null ,所以不能输入null } if (Object .create ) { return Object .create (p); // Object .create 是es6才有的方法,要判断浏览器是否支持该方法 } // 如果浏览器不支持Object .create 就用其他方法来创建新对象 if (typeof(p) !== 'object' && typeof(p) !== 'function' ) { throw TypeError(); } function foo(){}; foo.prototype = p; return new foo(); } let obj = {name : '呆呆' }; let new_obj = inherit (obj); new_obj.name = '爱死比' ; console.log(obj); // {name : "呆呆"}
2.3 属性访问错误 1 2 3 4 5 6 let p = null ;console .log (p.q.length ); let len = p && p.q && p.q.length ;
2.4 删除属性
delete运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上面删除它,而且这会影响到所有继承自这个原型 的对象)当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回true,如果delete后不是一个属性访问表达式,delete同样 返回true
1 2 3 4 let p = {name : 'as' }; console.log(p); // {name : "as"}delete p.name; console.log(p); // {}
2.5 检测是否存在某个属性
in运算符的左侧是属性名(字符串),右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回true。
1 2 3 4 let obj = {name : 'aaa' }; console.log('name' in obj); // true , 必须要用字符串,否则不管是否存在都会返回false console.log(name in obj); // false console.log('toString' in obj); // true ,obj 继承了 Object .prototype里面的属性
对象的hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。对于继承属性则返回false。
1 2 3 4 5 let obj = {name : 'aaa' };console .log (obj);console .log (obj.hasOwnProperty('name' )); console .log (obj.hasOwnProperty(name)); console .log (obj.hasOwnProperty('toString' ));
propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测到是自有属性且这个属性的可枚举性(enumerable attribute)为true时才返回true。
1 2 3 4 5 6 let obj = {name : 'aaa' };console .log (obj);console .log (obj.propertyIsEnumerable('name' )); console .log (obj.propertyIsEnumerable(name)); console .log (obj.propertyIsEnumerable('toString' )); console .log (Object.prototype.propertyIsEnumerable('toString' ));
除了使用in运算符之外,另一种更简便的方法就是使用’!==’判断一个属性是否是undefined
1 2 3 4 let obj = {name : 'aaa' };console .log (obj.name !== undefined ); console .log (obj.age !== undefined ); console .log (obj.toString !== undefined );
一个只能用in的特殊案例
1 2 3 4 5 6 7 let obj = {name : undefined };console .log ('name' in obj); console .log (obj.name !== undefined ); console .log (obj.hasOwnProperty('name' )); delete obj.name;console .log ('name' in obj);
参考资料:简单介绍javascript 中__proto__
属性的原理 JS重点整理之JS原型链彻底搞清楚 彻底理解js的原型链