JavaScript 对象原型以及原型链

对象原型&原型链

1.原型 [[Prototype]]

JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性,其实就是对于其余对象的引用。几乎全部的对象在建立时 [[Prototype]] 属性都会被赋予一个非空的值。javascript

思考一下以下代码:java

var myObject = {
    a:2
};
myObject.a; // 2

当你试图引用对象数据访问属性时会触发对象默认的内置[[Get]]操做,按照[[Get]]操做的算法首先从myObject对象内部查找是否有相同名称的属性,若是找到就会放回这个属性的值。算法

若是[[Get]]操做对象内部没法找到须要的属性,那么继续访问对象的[[prototype]]链:函数

let anotherObject = {
    a:2
};
// 建立一个关联到 anotherObject 的对象
let myObject = Object.create( anotherObject );
myObject.a; // 2

如今 myObject 对象的 [[Prototype]] 关联到了 anotherObject。显然 myObject.a 并不存在,可是尽管如此,属性访问仍然成功地(在 anotherObject 中)找到了值 2。可是若是 anotherObject 中也找不到 a 而且 [[Prototype]] 链不为空的话,就会继续查找下去。这个过程会持续到找到匹配的属性名或者查找完整条 [[Prototype]] 链。若是是后者的话,[[Get]] 操做的返回值是 undefined。
使用 for..in 遍历对象时原理和查找 [[Prototype]] 链相似,任何能够经过原型链访问到的属性都会被枚举。使用 in 操做符来检查属性在对象中是否存在时,一样会查找对象的整条原型链(不管属性是否可枚举):spa

let anotherObject = {
    a:2
};
// 建立一个关联到 anotherObject 的对象
let myObject = Object.create( anotherObject );
for (let k in myObject) {
    console.log("found: " + k);
}
// found: a
("a" in myObject); // true

所以,当你经过各类语法进行属性查找时都会查找 [[Prototype]]链,直到找到属性或者查找完整条原型链。prototype

Object.Prototype:

全部普通对象的[[prototype]]链指向内置的Object.Prototype。因为全部普通对象的[[prototype]]链源于Object.Prototype,因此它含有JavaScript中许多通用的功能。code

属性设置和屏蔽

给一个对象设置属性并不只仅是添加一个属性或者修改属性值,它包含如下过程:对象

myObject.foo = 'bar';

若是 myObject 对象中包含名为 foo 的普通数据访问属性,这条赋值语句只会修改已有的属性值
若是 foo 不是直接存在于 myObject 中,[[Prototype]] 链就会被遍历,相似 [[Get]] 操做。
若是原型链上找不到 foo,foo 就会被直接添加到 myObject 上。然而,若是 foo 存在于原型链上层,赋值语句 myObject.foo = "bar" 的行为就会有些不一样。
若是属性名 foo 既出如今 myObject 中也出如今 myObject 的 [[Prototype]] 链上层,那么就会发生屏蔽。myObject 中包含的 foo 属性会屏蔽原型链上层的全部 foo 属性,由于myObject.foo 老是会选择原型链中最底层的 foo 属性。blog

屏蔽比咱们想象中更加复杂。下面咱们分析一下若是 foo 不直接存在于 myObject 中而是存在于原型链上层时 myObject.foo = "bar" 会出现的三种状况:继承

  1. 若是在 [[Prototype]] 链上层存在名为 foo 的普通数据访问属性而且没有被标记为只读(writable:false),那就会直接在 myObject 中添加一个名为 foo 的新属性,它是屏蔽属性。
  2. 若是在 [[Prototype]] 链上层存在 foo,可是它被标记为只读(writable:false),那么没法修改已有属性或者在 myObject 上建立屏蔽属性。若是运行在严格模式下,代码会抛出一个错误。不然,这条赋值语句会被忽略。总之,不会发生屏蔽。
  3. 若是在 [[Prototype]] 链上层存在 foo 而且它是一个 setter,那就必定会调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会从新定义 foo 这个 setter。

2. 构造函数

function Foo() {
// ...
}
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true

Foo.prototype 默认(在代码中第一行声明时!)有一个公有而且不可枚举的属性 .constructor,这个属性引用的是对象关联的函数 Foo。此外,咱们能够看到经过“构造函数”调用 new Foo() 建立的对象也有一个 .constructor 属性,指向“建立这个对象的函数”

function NothingSpecial() {
    console.log( "Don't mind me!" );
}
var a = new NothingSpecial();//Don't mind me!
let b = NothingSpecial();//Don't mind me!
console.log(a)//NothingSpecial{}
console.log(b)//undefined

NothingSpecial 实际上是一个普通函数,但经过使用new调用的时候,它就会构造出一个对象而且赋给 a。这里的调用只是构造函数调用,可是NothingSpecial自己并非一个构造函数。

3. 检查原型关系

(1)instanceof:

function Foo() {
// ...
}
var a = new Foo();
console.log(a instanceof Foo) //true

instanceof 操做符的左操做数是一个普通的对象,右操做数是一个函数。instanceof 回答的问题是:在 a 的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象?惋惜,这个方法只能处理对象(a)和函数(带 .prototype 引用的 Foo)之间的关系。若是你想判断两个对象(好比 a 和 b)之间是否经过 [[Prototype]] 链关联,只用 instanceof没法实现。

(2)isPrototypeOf:

let a = {
    x:1
}
let b = Object.create(a);
let c = {
    y:2
};
console.log(a.isPrototypeOf(b));//true
console.log(c.isPrototypeOf(b));//false

isPrototypeOf是用来判断指定对象a是否存在于另外一个对象b的原型链中,是则返回true,不然返回false。

(3)Object.getPrototypeOf(对象):

function Foo(){
    //...
}
let a = new Foo();
let b = {
    x:1
}
console.log(Object.getPrototypeOf(a) == Foo.prototype);//true
console.log(Object.getPrototypeOf(b) == Foo.prototype);//false

Object.getPrototypeOf(对象)能够获取对象的[[prototype]]链

(4) proto :

function Foo(){
    //...
}

let a = new Foo();
console.log(a.__proto__);
console.log(Foo.prototype);
console.log(Foo.prototype.__proto__);
console.log(Object.prototype);
console.log(Object.prototype.__proto__);

输出结果:

11_50_06__03_17_2020.jpg

function Foo(){
    //...
}
let a = new Foo();
let c = {
    x:1
}
console.log(a.__proto__ === Object.prototype);//false
console.log(a.__proto__ === Foo.prototype);//true
console.log(Foo.prototype.__proto__ === Object.prototype);//true
console.log(c.__proto__ === Object.prototype);//true

proto 返回对象上一层[[prototype]]原型对象

4. 总结

若是要访问对象中并不存在的一个属性,[[Get]] 操做就会查找对象内部[[Prototype]] 关联的对象。这个关联关系实际上定义了一条“原型链”(有点像嵌套的做用域链),在查找属性时会对它进行遍历。

全部普通对象都有内置的 Object.prototype,指向原型链的顶端(好比说全局做用域),若是在原型链中找不到指定的属性就会中止。toString()、valueOf() 和其余一些通用的功能都存在于 Object.prototype 对象上,所以语言中全部的对象均可以使用它们。

关联两个对象最经常使用的方法是使用 new 关键词进行函数调用中会建立一个关联其余对象的新对象。使用 new 调用函数时会把新对象的 .prototype 属性关联到“其余对象”。带 new 的函数调用一般被称为“构造函数调用”,尽管它们实际上和传统面向类语言中的类构造函数不同。

虽然这些 JavaScript 机制和传统面向类语言中的“类初始化”和“类继承”很类似,可是 JavaScript 中的机制有一个核心区别,那就是不会进行复制,对象之间是经过内部的[[Prototype]] 链关联的。

出于各类缘由,以“继承”结尾的术语(包括“原型继承”)和其余面向对象的术语都没法帮助你理解 JavaScript 的真实机制(不只仅是限制咱们的思惟模式)。相比之下,“委托”是一个更合适的术语,由于对象之间的关系不是复制而是委托。

相关文章
相关标签/搜索