js原型与原型链

字数 3890 阅读 704 喜欢 3

js原型与原型链

prototype与proto与constructor区别

function Person() {

};
let person = new Person();
person.name = 'Jim';
console.log(person.name);// Jim

👆的例子就是我们使用构造函数去创建对象的方式。通过new关键字去创建了person实例对象。
下面我们进行具体的解读:

prototype

首先每个对象都具有prototype属性,这个属性使我们有能力想对象添加属性和方法。

function employee(name,job,born)
{
this.name=name;
this.job=job;
this.born=born;
}

var bill=new employee("Bill Gates","Engineer",1985);

employee.prototype.salary=null;
bill.salary=20000;
20000
bill
employee {name: "Bill Gates", job: "Engineer", born: 1985, salary: 20000}
var Area = new employee('Area Jim','Engineer',1993)
test.salary
null

通过上面的例子 我们可以看出我们可以通过使用prototype这个属性给我们的构造函数,或者对象添加属性和方法。

那prototype这个属性到底指向的是什么呢?

其实,函数的prototype属性指向的是一个对象,这个对象就是正在调用的构造函数而创建的实例的原型。也是上面例子中的bill和Area的原型。

我们理解了prototype指向的是谁,但是现在又有了一个新问题,就是什么是原型?

其实原型就是:每一个JavaScript对象(不包括null)在创建的时候与之关联的另外一个对象,这个对象就是我们所说的原型,每个对象都会从原型继承属性。

关系图

我们从图中可以看到构造函数和实例原型之间的关系,那么我我们应该怎么表示实例与实例原型的关系呢,也就是bill与employee.prototype之间的关系呢?👇

proto

 let flag = true
flag.__proto__
Boolean {false, constructor: ƒ, toString: ƒ, valueOf: ƒ}

let str = ''
str.__proto__
String {"", length: 0, constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}

let arr = []
arr.__proto__
[constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]

let number = 1
number.__proto__
Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, toString: ƒ, …}

let obj = {}
obj.__proto__
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

每个JavaScript对象(null除外)都具有一个属性,这个属性就是_proto_,这个属性会指向该对象的原型。

大家可以在控制台输入下面的代码尝试一下:

function Person() {

}
var person = new Person();
console.log(person.__proto__ === Person.prototype);
true
关系图2

既然实例对象与构造函数都指向原型,那原型是否有属性指向构造函数或者指向实例呢?

constructor

function Person() {

}
var person = new Person()
console.log(Person === person.prototype.constructor)
true

从上面的代码我们可以看出原型是指向构造函数的,即属性constructor属性。
因为一个构造函数是可以产生多个实例的,即每个实例原型的constructor属性都指向关联的构造函数。

关系图3

当获取person.constructor时,当前的person实例时没有constructor属性的,当读取不到的时候会查找person的原型Person.prototype中去读取:

person.constructor === Person.prototype.constructor
true

实例与原型的关系

当js执行环境在读取实例属性是,找不到属性,会查询与对象关联的原型中的属性,如果还查不到,就回去查找原型的原型,知道找到最顶层为止。

🌰

function Person() {

}
Person.prototype.name = 'Jim'
var person = new Person()
person.name = 'Kimi'
console.log(person.name) //Kimi
delete person.name
console.log(person.name) //Jim

从上面的🌰我们可以看到 我们给实例的person对象添加了新的name值,并且打印person.name时,结果为Kimi,当我们把实例对象person的name属性删除时,再去读取person.name时,这是在person这个实例中name属性已经被删除了,那么js就会去person的原型person.proto,也就是Person.prototype中去查找name属性,这是返回的name为Jim。

如果在原型中还没有找到呢?原型的原型又是啥?

原型的原型

前面我们提到了原型是一个对象,既然原型是一个对象,那么我们可以使用最原始的方法去声明创建它:

var obj = new Object()
obj.name = 'Jim'
console.log(obj.name) //Jim

所以原型对象是通过Object构造函数生成的,也就是说实例的proto指向的是构造函数的prototype。

关系图4

原型的原型的原型是啥 --- 原型链

console.log(Object.prototype.__proto__)
null

null到底是啥?代表什么?

null表示"没有对象",即该处不应该有值。

所以当查询属性查询到了Object.prototype就可以停止了。

关系5

⬆️蓝色的这条线代表的就是原型链。

proto ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。

最后

饮用《你不知道的JavaScript》中的话:继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。