关键字:
1.构造函数
2.prototype的使用
3.原型链
4. constructor
5.Object.create()
6. Object.prototype._ proto _
7.获取原型对象方法的比较
写作原因:理清原型的诸多概念
1.什么是构造函数?
构造函数形如👇:
function Cat (name, color) {
this.name = name;
this.color = color;
}
var cat1 = new Cat('花花', '黑色');
cat1.name // '花花'
cat1.color // '黑色'
构造函数的缺点:
这样做是对系统资源的浪费,因为同一个构造函数的对象实例之间,无法共享属性.
2.为什么有prototype这个属性?
JavaScript 的每个对象都继承另一个对象,后者称为“原型”(prototype)对象。只有null除外,它没有自己的原型对象。
原型对象上的所有属性和方法,都能被派生对象共享。这就是 JavaScript 继承机制的基本设计。
通过构造函数生成实例对象时,会自动为实例对象分配原型对象。每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。
function Animal (name) {
this.name = name;
}
Animal.prototype.color = '黑色';
var cat1 = new Animal('花花');
var cat2 = new Animal('喵喵');
cat1.color // '黑色'
cat2.color // '黑色'
上面代码中,构造函数Animal的prototype对象,就是实例对象cat1和cat2的原型对象。在原型对象上添加一个color属性。结果,实例对象都能读取该属性。
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
当实例对象本身没有某个属性或方法的时候,它会到构造函数的prototype属性指向的对象,去寻找该属性或方法。这就是原型对象的特殊之处。
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
总结一下:原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
Animal.prototype.walk = function () {
console.log(this.name + ' is walking');
};
上面代码中,Animal.prototype对象上面定义了一个walk方法,这个方法将可以在所有Animal实例对象上面调用。
由于 JavaScript 的所有对象都有构造函数(只有null除外),而所有构造函数都有prototype属性(其实是所有函数都有prototype属性),所以所有对象都有自己的原型对象。
3.原型链是什么?有什么作用?
对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性指向的那个对象。那么,Object.prototype对象有没有它的原型呢?回答可以是有的,就是没有任何属性和方法的null对象,而null对象没有自己的原型。
Object.getPrototypeOf(Object.prototype)// null
上面代码表示,Object.prototype对象的原型是null,由于null没有任何属性,所以原型链到此为止。
“原型链”的作用是:
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
下面的代码可以找出,某个属性到底是原型链上哪个对象自身的属性。
function getDefiningObject(obj, propKey) {
while (obj && !{}.hasOwnProperty.call(obj, propKey)) {
obj = Object.getPrototypeOf(obj);
}
return obj;
}
//对象实例的hasOwnProperty方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上。
Date.hasOwnProperty('length')// true
4.constructor
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
function P() {}
P.prototype.constructor === P// true
由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。
function P() {}
var p = new P();
p.constructor
// function P() {}
p.constructor === P.prototype.constructor
// true
p.hasOwnProperty('constructor')
// false
上面代码中,p是构造函数P的实例对象,但是p自身没有contructor属性,该属性其实是读取原型链上面的P.prototype.constructor属性。
constructor属性的作用,是分辨原型对象到底属于哪个构造函数。
function F() {};
var f = new F();
f.constructor === F // true
f.constructor === RegExp // false
上面代码表示,使用constructor属性,确定实例对象f的构造函数是F,而不是RegExp。
有了constructor属性,就可以从实例新建另一个实例。
function Constr() {}
var x = new Constr();
var y = new x.constructor();
y instanceof Constr // true
上面代码中,x是构造函数Constr的实例,可以从x.constructor间接调用构造函数。
这使得在实例方法中,调用自身的构造函数成为可能。
Constr.prototype.createCopy = function () {
return new this.constructor();
};
由于constructor属性是一种原型对象与构造函数的关联关系,所以修改原型对象的时候,务必要小心。
function A() {}
var a = new A();
a instanceof A // true
function B() {}
A.prototype = B.prototype;
a instanceof A // false
上面代码中,a是A的实例。修改了A.prototype以后,constructor属性的指向就变了,导致instanceof运算符失真。
instanceof运算符用来比较一个对象是否为某个构造函数的实例
所以,修改原型对象时,一般要同时校正constructor属性的指向。
//推荐写法
C.prototype.method1 = function (...) { ... };
instanceof运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。
var v = new Vehicle();
v instanceof Vehicle // true
上面代码中,对象v是构造函数Vehicle的实例,所以返回true。
instanceof运算符的左边是实例对象,右边是构造函数。它会检查右边构建函数的原型对象,是否在左边对象的原型链上。
由于instanceof对整个原型链上的对象都有效,因此同一个实例对象,可能会对多个构造函数都返回true。
var d = new Date();
d instanceof Date // true
d instanceof Object // true
上面代码中,d同时是Date和Object的实例,因此对这两个构造函数都返回true。
除了上面这种继承null的特殊情况,JavaScript 之中,只要是对象,就有对应的构造函数。因此,instanceof运算符的一个用处,是判断值的类型。
instanceof运算符只能用于对象,不适用原始类型的值。
此外,对于undefined和null,instanceOf运算符总是返回false。
undefined instanceof Object // false
null instanceof Object // false
利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题。
function Fubar (foo, bar) {
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
}
else {
return new Fubar(foo, bar);
}
}
上面代码使用instanceof运算符,在函数体内部判断this关键字是否为构造函数Fubar的实例。如果不是,就表明忘了加new命令。
5.Object.create()
生成实例对象的常用方法,就是使用new命令,让构造函数返回一个实例。但是很多时候,只能拿到一个实例对象,它可能根本不是由构建函数生成的,那么能不能从一个实例对象,生成另一个实例对象呢?
JavaScript 提供了Object.create方法,用来满足这种需求。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承继承原型对象的属性。
// 原型对象
var A = {
print: function () {
console.log('hello');
}
};
// 实例对象
var B = Object.create(A);
B.print() // hello
B.print === A.print // true
上面代码中,Object.create方法以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
6.Object.prototype._ proto _
_ proto _ 属性(前后各两个下划线)可以改写某个对象的原型对象。
var obj = {};
var p = {};
obj._ proto _ = p;
Object.getPrototypeOf(obj) === p // true
上面代码通过_ proto _ 属性,将p对象设为obj对象的原型。
根据语言标准,_ proto _ 属性只有浏览器才需要部署,其他环境可以没有这个属性,而且前后的两根下划线,表示它本质是一个内部属性,不应该对使用者暴露。因此,应该尽量少用这个属性,而是用Object.getPrototypeof()(读取)和Object.setPrototypeOf()(设置),进行原型对象的读写操作。
7.获取原型对象方法的比较
_ proto _ 属性指向当前对象的原型对象,即构造函数的prototype属性。
var obj = new Object();
obj._ proto _ === Object.prototype
// true
obj._ proto _ === obj.constructor.prototype
// true
上面代码首先新建了一个对象obj,它的_ proto _ 属性,指向构造函数(Object或obj.constructor)的prototype属性。所以,两者比较以后,返回true。
因此,获取实例对象obj的原型对象,有三种方法。
obj._ _proto_ _
obj.constructor.prototype
Object.getPrototypeOf(obj)
上面三种方法之中,前两种都不是很可靠。最新的ES6标准规定,_ proto _ 属性只有浏览器才需要部署,其他环境可以不部署。而obj.constructor.prototype在手动改变原型对象时,可能会失效。
推荐使用第三种Object.getPrototypeOf方法,获取原型对象。