이 글에서는 JavaScript의 객체 프로토타입과 프로토타입 체인을 소개합니다. 도움이 필요한 친구들이 모두 참고할 수 있기를 바랍니다.
모두가 다음과 같이 map
을 사용했다고 생각합니다. 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
是数组实例,原型是 Arrayarr.__proto__
是数组的原型,原型是 Objectarr.__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
Object.defineProperty(Vue.prototype, '$date', { value: dateTimeFormat }) // 之后就可以这样用 vm.$date(dateObj)
arr
자체에는 map
속성이 설정되어 있지 않은데 왜 map
함수를 사용할 수 있습니까? 🎜🎜인쇄하여 확인하세요. 🎜Person.prototype.hello = function () { console.log(`Bye ${this.name}.`) }gary.hello() // Bye Gary. alice.hello() // Bye Alice.
__proto__
라는 개체가 나타납니다. 확장하면 Array 개체에서 사용할 수 있는 모든 기능을 볼 수 있습니다. map
함수가 포함되어 있으며 이것이 바로 예제에서 호출된 arr.map
함수입니다. 🎜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: ƒ}
__proto__
가 여기에 나타납니다. 객체라고도 함 🎜프로토타입 🎜으로. 🎜🎜클래스 정의, 인스턴스 생성, 상속 지정 등을 통해 속성과 메서드를 전달하는 Java, C# 및 기타 🎜클래스 기반 객체 지향 언어와는 다릅니다. Javascript는 🎜프로토타입 기반 언어🎜입니다. 확립된 프로토타입 객체는 새로운 객체가 생성될 때 객체의 프로토타입이 어떤 프로토타입 객체를 참조해야 하는지 지정합니다. 🎜🎜객체의 속성이나 메소드를 호출할 때 객체 자체에 이 속성이나 메소드가 없으면 JavaScript는 자동으로 프로토타입에서 메소드를 찾습니다. 이것이 바로 arr.map
을 호출할 수 있는 이유입니다. code>를 직접적으로 설명하고 잘못될 수 없는 이유를 설명합니다. 🎜🎜🎜프로토타입 체인🎜🎜🎜이전 예에서 __proto__
개체에 여전히 __proto__
속성이 있다는 것을 발견했을 수 있습니다. 🎜rrreee🎜위 메커니즘에서는 언제든지 프로토타입은 생성될 때 객체에 바인딩됩니다. 객체 프로토타입 자체도 객체이므로 당연히 이 예제에서 볼 수 있습니다. 🎜arr는 Array 인스턴스이고 프로토타입은 Array입니다.
arr.__proto__
는 배열의 프로토타입이고 프로토타입은 Object입니다.arr .__proto__.__proto__
객체의 프로토타입입니다. 프로토타입은 null
arr.__proto__.__proto__.__proto__
입니다. null이며 속성이 없습니다추가: JavaScript에서는 거의 모든 프로토타입 체인의 끝이 Object이고 마지막으로 null
을 가리킵니다.
🎜🎜프로토타입 사용하기🎜🎜🎜 지금까지 설명을 마쳤으니 이제 코드를 작성해 보겠습니다. 다음으로 프로토타입 생성, 설정 및 수정을 연습해 보겠습니다. 🎜🎜먼저 새 개체 생성자를 만듭니다. 🎜rrreee🎜위의 예에서는 간단한 개체 생성자 Person()
을 만들고 생성자에서 개체 속성을 설정합니다. 객체의 메소드에서는 중복된 메모리 소모를 방지하기 위해 객체별로 별도의 복사본을 가질 필요가 없으므로 이전의 Array.prototype.map
예시와 같이 객체를 사용해야 합니다. 메소드는 프로토타입 객체(Person.prototype
)로 설정되므로 이 생성자에 의해 생성된 객체는 이러한 메소드를 공유할 수 있습니다. 마지막으로, 새로운 Person
객체를 생성하고 getPrototypeOf(obj)
를 통해 새로 생성된 객체의 프로토타입을 얻습니다. 🎜Q: 프로토타입 객체를 얻기 위해__proto__
를 직접 사용하면 어떨까요?
A:__proto__
는 거의 모든 브라우저에서 지원되지만 이는 비표준 속성이기 때문에getPrototypeOf
를 통해 객체의 프로토타입을 얻는 것이 올바른 방법입니다. . 🎜알림:Person.prototype
은Person
의 프로토타입이 아니지만 생성자가 실행된 후 생성된 새 객체의 프로토타입은 절대 🎜를 대체하지 않습니다. 생성자 프로토타입
속성 🎜이 🎜 객체의 프로토타입 🎜과 혼동됩니다! 🎜
Person
을 상속합니다. 🎜rrreee🎜새 개체 Engineer의 프로토타입을 <code>Engineer.prototype
으로 지정하여 해당 프로토타입이 Person.prototype
에서 상속되도록 하고 마지막으로 Engineer.prototype 생성자
를 재설정합니다. > 생성자가 자신을 다시 가리키도록 하면 가장 기본적인 프로토타입 상속이 완료됩니다. 🎜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
语法糖,使开发者体验提升了很多。下面把前面的例子用 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의 객체 프로토타입 및 프로토타입 체인 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!