研究之前,我们先复习下?
构造函数
任何函数,只要通过new操作符来调用,那么它就可以作为构造函数。
prototype
prototype是函数的独有属性,这个属性是一个指针,从一个函数指向一个对象,它的含义是函数的原型对象,也就是这个构造函数所创建的实例的原型对象。它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。
我们去控制台打印看下
//创建一个函数
function fn(){}
//看看函数的prorotype是什么东东?打印出来的是否是个对象
console.log(fn.prototype);
控制台打印出来的是个对象,里面有两个参数,constructor指向构造函数fn ,__proto__指定Object
__proto__
__proto__是对象的独有属性,JS中函数也是一种对象,所以也有__proto__属性,
__proto__
属性都是由一个对象指向一个对象,即指向它们的原型对象。它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__
属性所指向的那个对象里找,如果还没有找到,继续往上找,直到原型链为null为止。
我们验证下
function fn(){}
var f1=new fn()
//f1.__proto__是否指向fn的原型fn.prototype
console.log(f1.__proto__);
console.log(f1.__proto__===fn.prototype);
控制台输出的 f1.__proto 和上面 fn.prototype 输出一样,可以得出 f1.__proto__ === fn.prototype 为 true
constructor
constructors属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,每个对象都有构造函数。
来看看
function fn(){}
var obj=new Object();
var f1=new fn()
console.log(fn.prototype.constructor,'f1')
console.log(fn.constructor,'fn')
console.log(obj.constructor,'obj')
//f1实例的constructor是指向它的构造函数 fn
console.log(f1.constructor===fn) ; //true
控制台
f1的 constructor 指向它的构造函数 fn , fn 的 constructor 指向 Function ,obj 的 constructor 指向它的构造函数 Object。
经过上面的了解估计你大概已经懂了原型链,现在我们来总结下什么是原型链?
每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为
null(null
为原型链的终点),
由以上这种通过__proto__
属性来连接对象直到null
的一条链即为我们所谓的原型链。
经过本仙女多次画图,终于得出下面这个简单明了的关系图说明:
举个例子来说明下
//定义构造函数fn
function fn(){}
fn.prototype.name='M'
//通过new创建fn的实例对象f1
var f1=new fn()
f1.age=23
//实例对象的__proto__指向构造函数的原型对象
console.log(f1.__proto__===fn.prototype);//true
//原型对象的constructor指向构造函数
console.log(fn.prototype.constructor===fn);//true
再来看下这个原型的原型对象是什么:
console.log(fn.prototype.__proto__)
控制台输出的对象的constructor指向的是Object()这个构造函数,所以fn.prototype.__proto__===Object.prototype为true,在继续打印fn.prototype.__proto__.__proto__控制台输出null,这个函数的原型链到此结束。
所以这个f1的原型链就是:
于是我们就可以得出:在原型链中的指向是
函数 → 构造函数 → Function.prototype → Object.protype → null
实例访问属性或者方法的时候,遵循以为原则:
- 如果实例上面存在,就用实例本身的属性和方法。
- 如果实例上面不存在,就会顺着__proto__的指向一直往上查找,查找就停止。
举个例子说明如何修改原型链
//定义构造函数fn1
function fn1(){}
fn1.prototype.method=function(){
return '别搞错,我是fn1原型链上面的method';
}
//添加属性到prototype上
fn1.prototype.name="fn1";
//定义构造函数fn2
function fn2(){}
fn2.prototype.method=function(){
return '哎呀,你调用的是fn2上面的method';
}
//通过new创建fn的实例对象f1
var f1=new fn1()
//添加属性到f1对象上
f1.age=23;
console.log( f1.method()); //别搞错,我是fn1原型链上面的method
//输出f1实例上面的age : 23
console.log( f1.age );// 23
//f1上没有这个属性,就会沿着原型链去原型对象上去找,因为原型对象上有name属性,所以输出“fn1”
console.log( f1.name ) ; //fn1
//f1上没有这个属性,就会沿着原型链去原型对象上去找,原型对象上也没有这个方法,输出 undefined
console.log( f1.obj ) ; //undefined
//将f1的__proto__指向fn2的原型
f1.__proto__=fn2.prototype;
//此时f1实例的原型对象已经变成了fn2的原型对象
console.log( f1.method()); //哎呀,你调用的是fn2上面的method
一般来说,我们不建议手动去修改某个对象的原型,这会破坏掉原来的原型链。
使用不同的方法来创建对象和生成原型链
使用语法结构创建的对象
var o = {a: 1};
// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null
function f(){
return 2;
}
// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null
使用构造器创建的对象
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'。
// 在 g 被实例化时,g.[[Prototype]] 指向了 Graph.prototype。
使用 Object.create 创建的对象
ECMAScript 5 中引入了一个新方法:Object.create。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype
使用 class 关键字创建的对象
ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括
class
,constructor
,static
,extends
和super
。
"use strict";
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
性能
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。
遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype
继承的 hasOwnProperty
方法 。
console.log(g.hasOwnProperty('vertices'));
// true
console.log(g.hasOwnProperty('nope'));
// false
console.log(g.hasOwnProperty('addVertex'));
// false
console.log(g.__proto__.hasOwnProperty('addVertex'));
// true
hasOwnProperty
是 JavaScript 中唯一一个处理属性并且不会遍历原型链的方法。(译者注:原文如此。另一种这样的方法:Object.keys()
)
注意:检查属性是否为 undefined
是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined
。