侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

JS原型链01-原型链的粗浅理解

barwe
2022-03-25 / 0 评论 / 0 点赞 / 896 阅读 / 2,103 字
温馨提示:
本文最后更新于 2022-04-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

绝大部分支持面向对象的编程语言都会使用 class 关键字来定义一个类,然而 ES6 之前的 JavaScript 不知道咋想的,没有这个关键字。但是它又想实现跟类一样的功能该怎么办呢?这就用到了 原型 (prototype)。

封装继承多态 是类的三大特性,其本质上都是代码的复用,以及相同代码的基础上加上一些自己特有的东西。

在 JavaScript 中,对象 看起来就像是一个由 {} 定义的字典或者哈希,一个简单的对象并不需要我们手动去 new 它,例如 {name: 'barwe', age: 8} 就是一个 JavaScript 对象。

每个 JavaScript 对象都会有很多成员,例如简单的静态数据成员、函数和方法,以及一些其它的特殊的属性。

在对象的特殊属性中,有这样一个 [[Prototype]] 内置属性为所有对象所共有,它指向了另一个对象,其作用是:当我们尝试访问当前对象上一个不存在的成员时,解释器会自动去查找这个 [[Prototype]] 属性所指向的那个对象的成员,依次类推,直到查找到 [[Prototype]] 指向 null 的那个对象。

我们定义一个简单对象:

const x = { name: 'barwe' }

在 Firefox 中查看时,它有两个属性:

{
	name: 'barwe',
	<prototype>: Object;
}

其中的 <prototype> 属性就是解释器自动添加的属性,每个浏览器的显示可能存在差异。

它指向了另一个对象,我们可以通过 x.__proto__ 访问到它:

// x.__proto__
Object {
    __defineGetter__: function __defineGetter__()
    __defineSetter__: function __defineSetter__()
    __lookupGetter__: function __lookupGetter__()
    __lookupSetter__: function __lookupSetter__()
    __proto__:  null
    constructor: function Object()
    hasOwnProperty: function hasOwnProperty()
    isPrototypeOf: function isPrototypeOf()
    propertyIsEnumerable: function propertyIsEnumerable()
    toLocaleString: function toLocaleString()
    toString: function toString()
    valueOf: function valueOf()
    <get __proto__()>: function __proto__()
    <set __proto__()>: function __proto__()
}

我们可以看到,x.__proto__ 属性指向的对象拥有很多方法,按照最开始约定的递归查找规则,这些属性和方法都能被 x 对象直接使用,所以我们能通过 x.toString()x 转化为一个字符串。

此外,x.__proto__ 指向的对象,本身也有一个 __proto__ 属性,它指向了 null,这表示递归查找到此结束了,这个 __proto__ 属性指向 null 的对象就是所有对象的最终归宿。

在这个过程中,我们实际上只谈到 对象,而忽略了 的定义。实际上在 JavaScript 中我们可以非常轻易的创建一个对象。通过修改创建的对象的 __proto__ 的指向,可以复用其指向的对象的属性和方法。修改多个对象使它们的 __proto__ 属性指向同一个对象,可以让它们共享这个对象的成员。这就有点继承的味道了,但是明显有很大的缺陷,数据是共享的,修改了某个对象 __proto__ 指向对象的成员,其它依赖于这个对象的子对象也会收到影响。

对于对象,我们可以通过 __proto__ 属性来获取当前对象的 原型,即创建当前对象的 模板对象

然而如果我们需要给某一类的所有对象添加一个共同的属性,例如将字符串 "true" 转换成布尔值 true 的成员方法,我们不可能去修改每个定义的字符串对象的属性,因为这个方法明显是所有该类对象共有的,我们应该将它写在字符串类的父类上。我们也不可能为每个字符串对象的 __proto__ 属性指向的对象手动添加这个成员,虽然可以实现功能,但是想想就很呆:为了给类添加某个成员,却要通过对象实现。

JavaScript 为每个类提供了一个 prototype 属性,例如 String.prototype。通过这个 prototype 属性,我们能为类指定一个原型对象,所有基于这个类实例化的对象都会自动以这个原型对象作为模板,即对象的 __proto__ 属性指向这个原型对象,从而为这个类绑定了一下特殊的成员。

String.prototype.version = '1.0'
x = 'x'
x.version //=> 1.0

这样,所有的对象都通过原型对象串起来了,通过层层递进搜索,实现了代码的复用。

总之,JavaScript 企图通过 原型 来实现 的功能,但它有不完全同于经典类的使用方式。

0

评论区