关于javascript:什么是原型链

10次阅读

共计 3934 个字符,预计需要花费 10 分钟才能阅读完成。

对象原型

置信大家都这样用过 map

let arr = [0, 1, 2]
let doubleArr = arr.map(c => c * 2)
console.log(doubleArr) // 0, 2, 4

不晓得你有没有想过,arr 自身并没有设定 map 属性,那为什么能够用 map 这个函数呢?

把它打印进去看看:

console.log(arr)
// 0: 0
// 1: 1
// 2: 2
// length: 3
// __proto__: Array(0)

呈现了一个名为 __proto__ 的对象,如果再将其开展,就会看到所有 Array 对象能够应用的函数;当然咱们也能够在其中找到 map 函数,而这也正是例子中所调用的 arr.map 这个函数:

console.log(arr.map === arr.__proto__.map) // true

这里呈现的 __proto__ 对象,也就是所谓的 原型对象(Prototype)

不同于 Java、C# 等 基于类(Class) 的面向对象语言,通过定义类、创立实例、指定继承等形式来传递属性及办法;Javascript 则是个 基于原型(Prototype)的对语言,通过事后建设出的原型对象,当新对象建设时指定对象的原型要参照哪个原型对象。

而当咱们调用对象的属性或办法时,若对象自身没有这个属性或办法,JavaScript 会主动寻找它原型中的办法,这也就是为什么能够间接调用 arr.map 而不会出错的起因。

原型链

你可能曾经发现在后面的例子中,__proto__ 对象里依然有 __proto__ 属性:

console.log(arr.__proto__) // Array 的 Prototype
console.log(arr.__proto__.__proto__) // Object 的 Prototype
console.log(arr.__proto__.__proto__.__proto__) // null

在上述机制中,每当对象建设时都会绑定原型,既然对象都有原型,对象原型自身也是对象,天然也不例外;由这个例子中咱们能够看出:

  • arr 是数组实例,原型是 Array
  • arr.__proto__ 是数组的原型,原型是 Object
  • arr.__proto__.__proto__ 是对象的原型,原型是 null
  • arr.__proto__.__proto__.__proto__ 是 null,没有任何属性

因为每个对象都有原型,这样就造成了一个关联一个、层层相互依赖的从属关系,咱们把它们叫做 原型链(Prototype Chain);通过这种机制,让对象得以应用原型中的属性和办法,并凭借原型链一层一层的按程序继承,让对象能领有原型链上所有原型的性能,这就是 JavaScript 对象背地的运作机制。

补充:在 JavaScript 中,简直每个原型链的末端都会是 Object,并最初指向到 null

应用原型

说了这么多,该来点代码了,接下来就来练习一下原型的建设、设定及批改吧。

先来创立一个新的对象构造函数:

function Person(name) {this.name = name}Person.prototype.hello = function () {console.log(`Hello ${this.name}.`)
}let gary = new Person('Gary')
gary.hello() // Hello Gary.Object.getPrototypeOf(gary) // {hello: ƒ, constructor: ƒ}

下面的例子创立了一个简略的对象构造函数 Person(),并在构造函数中设定对象属性。对象的办法中,因为办法不须要让每个对象都单独领有一份,以防止造成冗余的内存耗费,应该要像后面 Array.prototype.map 的例子那样把对象的办法设定给原型对象(Person.prototype),让这个构造函数创立进去的对象都能够共用这些办法。最初建设一个新的 Person 对象,并通过 getPrototypeOf(obj) 获取新产生对象的原型。

Q:为什么不间接用 __proto__ 获取原型对象?
A:因为尽管 __proto__ 被简直所有的浏览器反对,但它是非规范属性;通过 getPrototypeOf 获得对象的原型是正确的办法。

揭示:Person.prototype 不是 Person 的原型,而是构造函数执行后所建设的新对象的原型;千万不要把 构造函数的 prototype 属性 对象的原型 搞混!

原型继承

接着再创立新的对象原型,并继承 Person

function Engineer(name, skill) {Person.call(this, name)
  this.skill = skill
}
Engineer.prototype = Object.create(Person.prototype)
Engineer.prototype.constructor = Engineerlet alice = new Engineer('Alice', 'JavaScript')
alice.hello() // Hello Alice.
console.log(alice.skill) // JavaScriptObject.getPrototypeOf(alice) 
// Person {constructor: ƒ}

这里建设了新的对象 Engineer 的原型,并通过 Engineer.prototype 的指定,让它的原型继承自 Person.prototype,最初再从新设定 Engineer.prototype.constructor,让构造函数从新指回本人;这样就实现了最根本的原型继承。

Q:为什么须要从新设定 constructor
A:这边功过 Object.create 复制了 Person.prototype 的全副属性,连同 constructor 属性都会被笼罩掉,如果 constructor 属性谬误,在做 instanceof 判断时会产生谬误的后果;因而这边设定继承时须要再次将 constructor 从新指定回构造函数自身。

批改原型

原型的援用、继承是间接参照到原型对象上,并非是在每个对象都复制一份原型;因而能够利用这个个性,在原型上减少自定义的属性和办法,让所有该类型的对象都能失去新办法;许多针对旧版浏览器的 Polyfill 就是这样实现的。

例如咱们在写 Vue 我的项目的时候,可能都有做过相似的操作,把共用性高的属性办法放到 Vue.prototype 中:

Object.defineProperty(Vue.prototype, '$date', { value: dateTimeFormat})
// 之后就能够这样用
vm.$date(dateObj)

这样确实很不便,但也要揭示开大家,当咱们在做原型批改的时候要特地小心。接着方才的例子,如果尝试对 Person 原型中的办法做个批改:

Person.prototype.hello = function () {console.log(`Bye ${this.name}.`)
}gary.hello() // Bye Gary.
alice.hello() // Bye Alice.

如后果所示,当对象原型做批改时,所有原型链上有这个原型的对象,统统都会受到影响,不论对象是在批改前还是批改后创立的。

倡议大家除非是 Polyfill,否则应该要竭力防止对原生对象的原型进行批改,避免造成可能的意外后果。

ES6 的 Class

看完后面这一大段,是不是感觉心很累?别放心,从 ES6 开始增加了 Class 语法糖,使开发者体验晋升了很多。上面把后面的例子用 Class 重构一下:

class Person {constructor (name){this.name = name}
  // 办法会主动放到 Person.prototype
  hello() {console.log(`Hello ${this.name}.`)
  }
}class Engineer extends Person {constructor (name, skill){super(name) // 调用 Person 的构造函数
    this.skill = skill
  }
}let alice = new Engineer('Alice', 'JavaScript')
alice.hello() // Hello Alice.Object.getPrototypeOf(alice) 
// Person {constructor: ƒ}

很不便,同样的性能,代码的可读性却进步了不少,繁琐的设定也都能交给语法主动帮你实现。不过不便的语法背地,底层依然是对象原型及原型链。

总结

以上是 JavaScript 中对于对象原型的阐明,心愿能帮你了解对象原型,在这个什么都是对象的语言中,充沛了解并把握对象原型,是成为业余码农必须要冲破的关卡之一。


本文首发微信公众号:前端先锋

欢送扫描二维码关注公众号,每天都给你推送陈腐的前端技术文章

欢送持续浏览本专栏其它高赞文章:

  • 深刻了解 Shadow DOM v1
  • 一步步教你用 WebVR 实现虚拟现实游戏
  • 13 个帮你进步开发效率的古代 CSS 框架
  • 疾速上手 BootstrapVue
  • JavaScript 引擎是如何工作的?从调用栈到 Promise 你须要晓得的所有
  • WebSocket 实战:在 Node 和 React 之间进行实时通信
  • 对于 Git 的 20 个面试题
  • 深刻解析 Node.js 的 console.log
  • Node.js 到底是什么?
  • 30 分钟用 Node.js 构建一个 API 服务器
  • Javascript 的对象拷贝
  • 程序员 30 岁前月薪达不到 30K,该何去何从
  • 14 个最好的 JavaScript 数据可视化库
  • 8 个给前端的顶级 VS Code 扩大插件
  • Node.js 多线程齐全指南
  • 把 HTML 转成 PDF 的 4 个计划及实现

  • 更多文章 …
正文完
 0