《你不知道的JavaScript》整理(四)——原型

简介: JavaScript中的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用。

一、[[Prototype]]


JavaScript中的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用。

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


当你试图引用对象的属性时会触发原型[[Get]]操作,比如myObject.a。

1. 第一步是检查对象本身是否有这个属性,如果有的话就使用它。

2. 如果a不在myObject中,就需要使用对象的[[Prototype]]链了。

 

1)Object.prototype

普通的[[Prototype]]链最终都会指向内置的Object.prototype。

1.png


2)属性设置和屏蔽


myObject.foo = "bar";

如 果 属 性 名foo既 出 现 在myObject中 也 出 现 在myObject的[[Prototype]]链 上 层, 那么就会发生屏蔽

myObject中包含的foo属性会屏蔽原型链上层的所有foo属性,因为myObject.foo总是会选择原型链中最底层的foo属性。

有些情况下会隐式产生屏蔽:


var anotherObject = {
  a: 2
};
var myObject = Object.create(anotherObject);
anotherObject.a; // 2
myObject.a; // 2 
anotherObject.hasOwnProperty("a"); // true
myObject.hasOwnProperty("a"); // false 
myObject.a++; // 隐式屏蔽!
anotherObject.a; // 2 
myObject.a; // 3
myObject.hasOwnProperty("a"); // true


++操作相当于myObject.a = myObject.a + 1。

因此++操作首先会通过[[Prototype]]查找属性a并从anotherObject.a获取当前属性值2,然后给这个值加1,接着用[[Put]]将值3赋给myObject中新建的屏蔽属性a。

 

二、“类”


JavaScript中只有对象

在JavaScript中,类无法描述对象的行,(因为根本就不存在类!)对象直接定义自己的行为。


1)“类”函数

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

a这个对象是在调用new Foo()时创建的,最后会被关联到这个“Foo.prototype”对象上。

在JavaScript中不能创建一个类的多个实例,只能创建多个对象,它们[[Prototype]]关联的是同一个对象。

从视觉角度来说,[[Prototype]]机制如下图所示,箭头从右到左,从下到上:


2.jpg


这个机制通常被称为原型继承,它常常被视为动态语言版本的类继承。

 

2)“构造函数

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


之所以认为Foo是一个“类”:

1. 其中一个原因是我们看到了关键字new,在面向类的语言中构造类实例时也会用到它。

2. 另一个原因是,看起来我们执行了类的构造函数方法,Foo()的调用方式很像初始化类时类构造函数的调用方式

在JavaScript中对于“构造函数”最准确的解释是,所有带new的函数调用

函数不是构造函数,但是当且仅当使用new时,函数调用会变成“构造函数调用”。

 

三、(原型)继承


function Foo(name) {
  this.name = name;
}
Foo.prototype.myName = function() {
  return this.name;
};
function Bar(name, label) {
  Foo.call(this, name);
  this.label = label;
}
// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype
Bar.prototype = Object.create(Foo.prototype);
// 注意!现在没有 Bar.prototype.constructor 了
// 如果你需要这个属性的话可能需要手动修复一下它
Bar.prototype.myLabel = function() {
  return this.label;
};
var a = new Bar("a", "obj a");
a.myName(); // "a" 
a.myLabel(); // "obj a"

原型继承的机制,是指a可以“继承”Foo.prototype并访问Foo.prototype的myName()函数。

面这两种方式是常见的错误做法

// 和你想要的机制不一样!
Bar.prototype = Foo.prototype;
// 基本上满足你的需求,但是可能会产生一些副作用 :( 
Bar.prototype = new Foo();


1. 第一种只是让Bar.prototype直接引用Foo.prototype对象。因此当你执行类似Bar.prototype.myLabel = ...的赋值语句时会直接修改Foo.prototype对象本身

2. 第二种的确会创建一个关联到Bar.prototype的新对象。但是它使用了Foo(..)的“构造函数调用”,如果函数Foo有一些副作用(比如写日志、修改状态、注册到其他对象、给this添加数据属性,等等)的话,就会影响到Bar()的“后代”

两种正确的把Bar.prototype关联到Foo.prototype的方法:

// ES6 之前需要抛弃默认的 Bar.prototype
Bar.ptototype = Object.create( Foo.prototype );
// ES6 开始可以直接修改现有的 Bar.prototype
Object.setPrototypeOf( Bar.prototype, Foo.prototype );


1)检查“类”关系


在传统的面向类环境中,检查一个实例(JavaScript中的对象)的继承祖先(JavaScript中的委托关联)通常被称为内省(或者反射)。


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


如何通过内省找出a的“祖先”(委托关联)呢?

1. 第一种站在“类”的角度来判断:


a instanceof Foo; // true


instanceof回答的问题是:在a的整条[[Prototype]]链中是否有指向Foo.prototype的对象?

这个方法只能处理对象(a)和函数(带.prototype引用的Foo)之间的关系。

 

2. 第二种判断[[Prototype]]反射的方法,它更加简洁:


Foo.prototype.isPrototypeOf( a ); // true

isPrototypeOf回答的问题是:在a的整条[[Prototype]]链中是否出现过Foo.prototype?

同样的问题,同样的答案,但是在第二种方法中并不需要间接引用函数(Foo),它的.prototype属性会被自动访问。

我们只需要两个对象就可以判断它们之间的关系。举例来说:


// 非常简单:b 是否出现在 c 的 [[Prototype]] 链中?
b.isPrototypeOf( c );


2)获取一个对象的[[Prototype]]链

1. 在ES5中,标准的方法是:


Object.getPrototypeOf( a );
console.log(Object.getPrototypeOf( a ) === Foo.prototype); // true


2. 浏览器也支持一种非标准的方法来访问内部[[Prototype]]属性:


a.__proto__ === Foo.prototype; // true


.__proto__的实现大致上是这样的:


Object.defineProperty(Object.prototype, "__proto__", {
  get: function() {
    return Object.getPrototypeOf(this);
  },
  set: function(o) {
    // ES6 中的 setPrototypeOf(..)
    Object.setPrototypeOf(this, o);
    return o;
  }
});


访问(获取值)a.__proto__时,实际上是调用了a.__proto__()(调用getter函数)。

虽然getter函数存在于Object.prototype对象中,但是它的this指向对象a,所以和Object.getPrototypeOf( a )结果相同

 

四、对象关联


[[Prototype]]机制就是存在于对象中的一个内部链接,它会引用其他对象

这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就会继续在[[Prototype]]关联的对象上进行查找。

这一系列对象的链接被称为“原型链”。


var foo = {
  something: function() {
    console.log("Tell me something good...");
  }
};
var bar = Object.create(foo);
bar.something(); // Tell me something good...


我们并不需要类来创建两个对象之间的关系,只需要通过委托来关联对象就足够了。

Object.create()的polyfill代码:


Object.create = function(o) {
  function F() {}
  F.prototype = o;
  return new F();
};


使用了一个一次性函数F,我们通过改写它的.prototype属性使其指向想要关联的对象,然后再使用new F()来构造一个新对象进行关联。

相关文章
|
6月前
|
JavaScript 前端开发
js开发:请解释原型继承和类继承的区别。
JavaScript中的原型继承和类继承用于共享对象属性和方法。原型继承通过原型链实现共享,节省内存,但不支持私有属性。
54 0
|
6月前
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(三)
深入JS面向对象(原型-继承)
54 0
|
6月前
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(一)
深入JS面向对象(原型-继承)
61 0
|
3月前
|
JavaScript 前端开发
如何在JavaScript中实现基于原型的继承机制
【8月更文挑战第14天】如何在JavaScript中实现基于原型的继承机制
31 0
|
2月前
|
JSON JavaScript 前端开发
js原型继承|26
js原型继承|26
|
2月前
|
JavaScript 前端开发
JavaScript prototype(原型对象)
JavaScript prototype(原型对象)
32 0
|
2月前
|
JavaScript 前端开发
JavaScript基础知识-原型(prototype)
关于JavaScript基础知识中原型(prototype)概念的介绍。
37 1
|
3月前
|
JavaScript 前端开发
JavaScript中什么是原型?有什么用?
JavaScript中什么是原型?有什么用?
23 1
|
3月前
|
JavaScript 前端开发 Java
什么是JavaScript原型对象
【8月更文挑战第2天】什么是JavaScript原型对象
61 9
|
5月前
|
设计模式 JavaScript 前端开发
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
JavaScript的继承机制基于原型链,它定义了对象属性和方法的查找规则。每个对象都有一个原型,通过原型链,对象能访问到构造函数原型上的方法。例如`Animal.prototype`上的`speak`方法可被`Animal`实例访问。原型链的尽头是`Object.prototype`,其`[[Prototype]]`为`null`。继承方式包括原型链继承(通过`Object.create`)、构造函数继承(使用`call`或`apply`)和组合继承(结合两者)。ES6的`class`语法是语法糖,但底层仍基于原型。继承选择应根据需求,理解原型链原理对JavaScript面向对象编程至关重要
137 7
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略