《你不知道的JavaScript(上)》读书笔记——原型

JS中每个对象都有原型

内置属性[[Prototype]]

书中提到:每个对象都有不可枚举的内置属性[[Prototype]]

这里具体化:对象可以通过obj.__proto__访问到它的原型,或者可以调用方法Object.getPrototypeOf(obj)访问其原型。

对于一些基础类型的变量,以number类型举例:
在这里插入图片描述

var a = 1时,a的类型是number,当访问a.__proto__时,JS引擎调用Number创建新的Number对象封装a,因此a.__proto__Number

对于引用类型object(包括空对象):
在这里插入图片描述
在这里插入图片描述
对象的原型是Object.prototype。没错它就叫“Object.prototype”,没有其他别名。

__proto__.prototype辨析

.prototype是函数特有的属性,例如Foo(){}函数的prototype属性是Foo.prototype。它是原型对象,没有别的别名)

  • 通常__proto__被称为隐式原型,指向构造该对象的构造函数的原型,构成原型链。
  • 通常.prototype被称为显示原型,用于实现基于原型的继承与属性共享。

后文中,我们将用隐式原型和显示原型的称呼区分proto和prototype

实际上__proto__属性并不存在于当前对象,它是Object.prototype的属性,当我们访问obj.__proto__时,实际上更像是在访问getter/setter:
在这里插入图片描述
prototype是函数自身的属性,原型对象prototype有个属性constructor指向函数自身。

当然函数也是对象,也有__proto__属性。

原型链

原型链是通对象的__proto__属性串联起来的链(可不是prototype属性)

原型链是什么样的

继续以Number类型为例:

通过在控制台上的输出
在这里插入图片描述

我们可以总结为下图:

在这里插入图片描述

  • Number的__proto__属性指向其构造函数的prototype属性,Number的构造函数是Object,因此Number.__ptoto__ == Object.prototype
  • 对象通过__proto__链接到原型,原型对象又通过__proto__链接到原型的原型从而构成了原型链
  • 原型链的终点是Object.prototype,(毕竟所有对象都是由Object创建),Object.__proto__指向null

原型链可以循环吗

在这里插入图片描述

尝试可以发现__proto__属性不允许有循环引用

基于原型链查找属性和方法

接着上面例子:var a = 1,当我们调用a.toString()(返回"1"),它的底层工作经历了如下的查找:

  1. 当前对象本身有没有toString方法(显然没有
  2. 遍历a的原型链中的对象,知道查找到某个对象含有toString方法为止。(这里沿着a->Number->Object.prototype, 然后在Object.prototype中找到了方法toString,然后返回/调用该方法
  3. 若遍历到原型链的终点,也没有找到想要的属性/方法,则返回undefined

在第2步返回/调用方法这里有个小细节,我们虽然在Object.prototype对象上找到对应的方法,但是调用方法的是对象a,也就是toString方法中的this指向a, 而非Object.prototype

可以沿着原型链查找,但是不能沿着原型链赋值

为对象原来没有的属性赋值,js在当前对象上创建新的属性并为其赋值。

var objA = {a:1}
// 将objA设为objB的原型
var objB = Object.create(objA)
objB.a // 1
objA.a // 1
objB.a = 2 // 会在objB对象上新增属性a并赋值为2
objB.a // 2
objA.a //1

上面的例子,为objB.a赋值后,它将objA.a覆盖,我们就不能通过objB.a访问到objA.a。

其他沿着原型链查找的例子:

for...in遍历对象,以及key in obj都会便利[[Prototype]]查找属性

关联

将b设为a的原型:var objB = Object.create(objA)

Object.create()

该函数底层实现类似于:

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

它做了以下几个步骤:

  1. 创建一个空的构造函数,
  2. 将传入的对象作为构造函数的显示原型
  3. 通过构造函数创建新对象,并返回。此时新对象的__proto__属性指向构造函数的显示原型。

因此我们可以总结出,var objB = Object.create(objA),就是将建一个新对象,新对象的__proto__属性指向第一个参数,即objA,然后将新对象赋值给objB

ES6新增方法Object.setPrototype(objB, objA)也可以达到此效果。

另,如果想要创建一个用于单纯存储数据的变量,而不想其有什么原型链可以Object.create(null)

内省/反射:判断对象是否在原型链上

  1. instanceof:a instanceof Foo,检查a的整条原型链是否有指向Foo.prototype的对象。

  2. isPrototypeOf: b.isPrototypeOf(c), b是否出现在c的原型链中

类 VS 对象关联

思考一个复杂的问题:以下两种委托方式有什么不同?

  1. var objB = Object.create(objA)
  2. var objB.prototype = Object.create(objA.prototype)
  • var objB = Object.create(objA):创建新变量objB并关联到objA, 也就是objB.__ptoto__ == objA

    这种方式创建的对象objB没有显示原型prototype,只有隐式原型__ptoto__

    function Foo () {}
    var Bar = Object.create(Foo)
    Bar.hasOwnProperty('prototype')  // false
    Bar.__proto__ == Foo  // true
    
  • var objB.prototype = Object.create(objA.prototype),即创建了新的对象覆盖原来的objB.prototype, 同时objB.prototype.__proto__ == objA.prototype, 类似于创建了类objB继承objA(只不过类名称的首字母通常大写,这里小写)。

    function Foo () {}
    function Bar () {}  // Bar需要先声明
    Bar.prototype = Object.create(Foo.prototype)
    Bar.hasOwnProperty('prototype')  // true
    Bar.__proto__   // ƒ() { [native code] }
    

两种的关系图如下:
在这里插入图片描述
左边的代码风格是对象关联风格,右边的代码风格我们称为风格。不过我们要明确一点js中没有真正的“类”,其底层还是靠委托实现。

以下是我对委托和类的个人理解:

委托:委托的思想是基于原型链的,说白了就是,若objB关联objA(objB.__proto__ == objA),那么无论是属性还是方法objB上查找不到的,那么就会去objB原型链上的对象上查找。查找到就会访问相应的属性/调用相应的方法(注意this的绑定)。看起来像把对象自身的方法和属性委托给它原型链上的对象。

传统的类的继承:子类继承父类,则子类会复制父类的属性和方法。(而委托只是引用了原型对象的属性/方法)

ES6有语法糖Class,其本质还是通过委托实现,并没实现传统的类

JavaScript中父类和子类的关系只存在于两者构造函数对应的.prototype对象中,构造函数之间并不存在直接联系。

类的代码风格实例

在这里插入图片描述

对象之间的关系结构图如下:

在这里插入图片描述

对象关联的代码风格实例

在这里插入图片描述

对象之间的关系结构图如下:

在这里插入图片描述

总结

JS中每个对象都具有[[prototype]]属性,原型属性连接形成原型链。

当我们访问当前对象上不存在的属性/方法时,就会沿着原型链上的对象查找属性/方法,如果找到就返回,否则直到原型链的尽头Object.prototype上都没有找到,就返回undefined。我们称之为委托:将属性/对象委托给原型链上的对象。

JS基于委托实现了面向类的代码风格,es6新增语法糖class,为开发者提供了更简洁可读的面向类代码风格。

但是js底层并没有实现传统的“类”,因为传统类中,子类继承父类是将父类的方法和属性复制,而js底层是通过委托实现。因此作者提供了一种更适合js的代码风格:对象关联。

对象关联代码风格的优势在于:首先它更接近js的实际机制;而且它使代码结构更加简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值