ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のオブジェクト プロトタイプとプロトタイプ チェーンを理解する

JavaScript のオブジェクト プロトタイプとプロトタイプ チェーンを理解する

青灯夜游
青灯夜游転載
2020-11-20 17:26:329556ブラウズ

この記事では、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__ オブジェクトは、いわゆる プロトタイプ オブジェクト (プロトタイプ) です。

Java、C#、その他の

クラスベースのオブジェクト指向言語とは異なり、プロパティとメソッドはクラスの定義、インスタンスの作成、継承の指定などによって転送されます。JavaScript は Based onプロトタイプ (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__
  • はオブジェクトのプロトタイプです。プロトタイプは
  • nullarr.__proto__ です。 .__proto__.__proto__
  • は null であり、属性はありません。
  • 各オブジェクトにはプロトタイプがあるため、階層ごとに相互に関連し依存する従属関係が形成されます。私たちはそれらを
  • プロトタイプ チェーン (プロトタイプ チェーン)
と呼びます。このメカニズムを通じて、オブジェクトはプロトタイプ内のプロパティとメソッドを使用し、プロトタイプ チェーンに依存してレイヤーごとに順番に継承することができます。プロトタイプ チェーン上のすべてのプロトタイプの機能を持ち、これが 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.prototypePerson

のプロトタイプではなく、コンストラクターの実行後に作成される新しいオブジェクトのプロトタイプです。コンストラクター # では を決して使用しないでください。 #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 をリセットします。コンストラクター、コンストラクターがそれ自体を指すようにします。これで、最も基本的なプロトタイプの継承が完了します。 <blockquote>Q:为什么需要重新设定 <code>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 のオブジェクト プロトタイプとプロトタイプ チェーンを理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。