Home >Web Front-end >JS Tutorial >Understanding object prototypes and prototype chains in JavaScript

Understanding object prototypes and prototype chains in JavaScript

青灯夜游
青灯夜游forward
2020-11-20 17:26:329691browse

This article will introduce you to the object prototype and prototype chain in JavaScript. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to everyone.

Understanding object prototypes and prototype chains in JavaScript

Object prototype

I believe everyone has used it like this map:

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

I don’t know if you have ever thought about it, arr itself does not set the map attribute, so why can you use the map function?

Print it out and see:

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

An object named __proto__ appears. If you expand it again, you will see that all Array objects can be used. function; of course we can also find the map function in it, and this is exactly the arr.map function called in the example:

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

appears here The __proto__ object is the so-called prototype object (Prototype).

Different from Java, C# and other class-based object-oriented languages, properties and methods are transferred by defining classes, creating instances, specifying inheritance, etc.; Javascript is a Based on prototype (Prototype) language, through the pre-established prototype object, when a new object is created, specify which prototype object the object's prototype should refer to.

When we call a property or method of an object, if the object itself does not have this property or method, JavaScript will automatically look for the method in its prototype, which is why we can call

arr.map# directly. ## without causing the error.

Prototype Chain

You may have noticed that in the previous example, the

__proto__

object still has the __proto__ attribute: <pre class="brush:php;toolbar:false">console.log(arr.__proto__) // Array 的 Prototype console.log(arr.__proto__.__proto__) // Object 的 Prototype console.log(arr.__proto__.__proto__.__proto__) // null</pre>In the above mechanism, the prototype will be bound every time the object is created. Since the object has a prototype, the object prototype itself is also an object, and naturally it is no exception; from this example we can see:

    arr
  • is an array instance, the prototype is Array
  • arr.__proto__
  • is the array prototype, the prototype is Object
  • arr.__proto__.__proto__
  • is the prototype of the object. The prototype is null
  • arr.__proto__.__proto__.__proto__
  • is null and has no attributes.
  • Since each object has a prototype, a subordinate relationship is formed that is related to each other and depends on each other layer by layer. We call them
Prototype Chain (Prototype Chain)

; Through this mechanism, objects can use the properties and methods in the prototype, and rely on the prototype chain to inherit in sequence layer by layer, so that the object can have the functions of all prototypes on the prototype chain. This is the operating mechanism behind JavaScript objects. Supplement: In JavaScript, the end of almost every prototype chain will be Object, and finally points to

null
.
Using prototypes

Having said so much, it’s time to come up with some code. Next, let’s practice the creation, setting and modification of prototypes.

First create a new object constructor:

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: ƒ}

The above example creates a simple object constructor

Person()

, and sets Define object properties. In the method of the object, since the method does not need to have its own copy for each object to avoid redundant memory consumption, the method of the object should be used as in the previous example of Array.prototype.map Set to the prototype object (Person.prototype) so that all objects created by this constructor can share these methods. Finally, create a new Person object and obtain the prototype of the newly generated object through getPrototypeOf(obj). Q: Why not directly use

__proto__
to obtain the prototype object? A: Because although __proto__
is supported by almost all browsers, it is a non-standard attribute; getting the prototype of the object through getPrototypeOf is the correct way. Reminder: Person.prototype

is not the prototype of Person, but the prototype of the new object created after the constructor is executed; never use in the constructor ##prototype The attributes are confused with the prototype of the object!

Prototype inheritance

Then create a new object prototype and inherit 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: ƒ}
Create here Get the prototype of the new object Engineer, and specify

Engineer.prototype to make its prototype inherit from Person.prototype, and finally resetEngineer.prototype.constructor, let the constructor point back to itself; this completes the most basic prototype inheritance.

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 中关于对象原型的说明,希望能帮你理解对象原型,在这个什么都是对象的语言中,充分理解并掌握对象原型,是成为专业码农必须要突破的关卡之一。

更多编程相关知识,请访问:编程课程!!

The above is the detailed content of Understanding object prototypes and prototype chains in JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete