您好,欢迎访问一九零五行业门户网

了解JavaScript中的对象原型和原型链

本篇文章带大家介绍一下javascript中的对象原型和原型链。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。
对象原型相信大家都这样用过  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 的 prototypeconsole.log(arr.__proto__.__proto__) // object 的 prototypeconsole.log(arr.__proto__.__proto__.__proto__) // null
在上述机制中,每当对象建立时都会绑定原型,既然对象都有原型,对象原型本身也是对象,自然也不例外;由这个例子中我们可以看出:
arr 是数组实例,原型是 arrayarr.__proto__ 是数组的原型,原型是 objectarr.__proto__.__proto__ 是对象的原型,原型是 nullarr.__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 中关于对象原型的说明,希望能帮你理解对象原型,在这个什么都是对象的语言中,充分理解并掌握对象原型,是成为专业码农必须要突破的关卡之一。
更多编程相关知识,请访问:编程课程!!
以上就是了解javascript中的对象原型和原型链的详细内容。
其它类似信息

推荐信息