前言
最近在整理原生JS的相关知识,发现关于原型和原型链涉及的知识对理解JS有很大的帮助。遂分享一下。
构造函数
所谓构造函数,就是提供了一个生成对象的模板并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。总的来说,构造函数就是对象的模板,对象就是构造函数的实例。
- 构造函数的首字母必须大写,用来区分于普通函数
- 内部使用的this对象,来指向即将要生成的实例对象
- 使用New来生成实例对象
构造函数的缺点
所有的实例对象都可以继承构造函数中的属性和方法。但是,同一个对象实例之间,无法共享属性。
function Person(name,height){
this.name=name;
this.height=height;
this.hobby=function(){
return 'watching movies';
}
}
var boy=new Person('keith',180);
var girl=new Person('rascal',153);
console.log(boy.name); //'keith'
console.log(girl.name); //'rascal'
console.log(boy.hobby===girl.hobby); //false
上面代码中,一个构造函数Person生成了两个对象实例boy和girl,并且有两个属性和一个方法。但是,它们的hobby方法是不一样的。也就是说,每当你使用new来调用构造函数放回一个对象实例的时候,都会创建一个hobby方法。这既没有必要,又浪费资源,完全可以被两个对象实例共享。
所以,构造函数的缺点就是:同一个构造函数的对象实例之间无法共享属性或方法。
prototype解决这个问题
为了解决构造函数的对象实例之间无法共享属性的缺点,js提供了prototype属性。
js中每个数据类型都是对象(除了null和undefined),而每个对象都继承自另外一个对象,后者称为“原型”(prototype)对象,只有null除外,它没有自己的原型对象。
原型对象上的所有属性和方法,都会被对象实例所共享。
function Person(name,height){
this.name=name;
this.height=height;
}
Person.prototype.hobby=function(){
return 'watching movies';
}
var boy=new Person('keith',180);
var girl=new Person('rascal',153);
console.log(boy.name); //'keith'
console.log(girl.name); //'rascal'
console.log(boy.hobby===girl.hobby); //true
//将hobby方法放在原型对象上,那么两个实例对象都共享着同一个方法。
上面代码中,当修改了原型对象的hobby方法之后,两个对象实例都发生了变化。这是因为对象实例其实是没有hobby方法,都是读取原型对象的hobby方法。也就是说,当某个对象实例没有该属性和方法时,就会到原型对象上去查找。如果实例对象自身有某个属性或方法,就不会去原型对象上查找。
boy.hobby=function(){
return 'play basketball';
}
console.log(boy.hobby()); //'play basketball'
console.log(girl.hobby()); //'swimming'
//上面代码中,boy对象实例的hobby方法修改时,就不会在继承原型对象上的hobby方法了。不过girl仍然会继承原型对象的方法。
总结:
- 原型对象的作用,就是定义所有对象实例所共享的属性和方法。
- prototype,对于构造函数来说,它是一个属性;对于对象实例来说,它是一个原型对象。
原型链
对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型对象本身对于对象实例来说也是对象,它也有自己的原型,所以形成了一条原型链(prototype chain)。
比如,a对象是b对象的原型,b对象是c对象的原型,
以此类推。所有一切的对象的原型顶端,都是Object.prototype,
即Object构造函数的prototype属性指向的那个对象。
当然,Object.prototype对象也有自己的原型对象,那就是没有任何属性和方法的null对象,而null对象没有自己的原型。即
Object.prototype = null
- 读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
- 如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。
- 一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
constructor
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
由于constructor属性是定义在原型(prototype)对象上面,意味着可以被所有实例对象继承。
function A(){};
var a=new A();
console.log(a.constructor); //A()
console.log(a.constructor===A.prototype.constructor);//true
//之所以可以a.constructor是因为A的原型对象有这个属性;
//而a会继承原型对象的属性和方法,
//所以,a才可以调用constructor属性。
constructor属性的作用
- 分辨原型对象到底属于哪个构造函数
function A(){};
var a=new A();
console.log(a.constructor===A) //true
console.log(a.constructor===Array) //false
instanceof运算符
instanceof运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。
因为instanceof对整个原型链上的对象都有效,所以同一个实例对象,可能会对多个构造函数都返回true。
注意,instanceof对象只能用于复杂数据类型(数组,对象等),不能用于简单数据类型(布尔值,数字,字符串等)。此外,null和undefined都不是对象,所以instanceof 总是返回false。
_ proto _
JS的万物都有__proto__
属性。用于指向创建它的构造函数的原型对象。
为什么不用
prototype
指向原型?
因为prototype
属性只有函数对象才拥有。
//创建一个构造函数
function People (name) {
this.name = name;
}
//实例化一个构造函数
let g1 = new People('蜡笔小新')
那么,则会有:
People.prototype === g1.__proto__
[[Prototype]] 其实就是__proto__
。ECMA-262第5版中管这个指针叫[[Prototype]]
。虽然在脚本中没有标准的方式访问[[Prototype]]
,但Firefox,Safari和Chrome在每个对象上都支持一个属性__proto__
;而在其他实现中,这个属性对脚本是完全不可见的。
Object和Function
Object对象体系下:
一切对象的原型链最终都是.... → Object.prototype → null
。例如定义一个num变量var num = 1
,则num的原型链为x → Number.prototype → Object.prototype → null
; 定义一个函数对象fnfunction fn() {}
,则fn的原型链为fn → Function.prototype → Object.prototype → null
;等等...
Function体系下:
不考虑null
的情况下(其中Object.prototype.__proto__ === null
),Object.prototype
为原型链的顶端,Function.prototype
继承Object.prototype
而产生,最后,Function
和Object
和其它构造函数继承Function.prototype
而产生。
注意:Function.prototype === Function.__proto__
为true
JS万物诞生记
总结
- 同一个对象实例之间无法共享属性。即实例1和实例2的属性不能共享。
- 构造函数的
prototype
属性叫做原型对象。(只有函数对象才有 prototype 属性) - 原型对象上的所有属性和方法,都会被对象实例所共享。
- 原型链:读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
- 如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。
- 一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
- prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
- 由于constructor属性是定义在原型(prototype)对象上面,意味着可以被所有实例对象继承。
- instanceof对象只能用于判断复杂数据类型(数组,对象等),不能用于简单数据类型(布尔值,数字,字符串等)
- JS的万物都有proto属性。用于指向创建它的构造函数的原型对象。
参考文章
[详解Javascript中prototype属性(推荐)]
最详尽的 JS 原型与原型链终极详解,没有「可能是」。(一)
最详尽的 JS 原型与原型链终极详解,没有「可能是」。(二)
最详尽的 JS 原型与原型链终极详解,没有「可能是」。(三)
彻底搞懂Object和Function的关系