对象原型相信大家都这样用过 let arr = [0, 1, 2]let doubleArr = arr.map(c => c * 2)console.log(doubleArr) // 0, 2, 4 不知道你有没有想过, 把它打印出来看看: console.log(arr)// 0: 0// 1: 1// 2: 2// length: 3// __proto__: Array(0) 出现了一个名为 console.log(arr.map === arr.__proto__.map) // true 这里出现的 不同于 Java、C# 等基于类(Class) 的面向对象语言,通过定义类、创建实例、指定继承等方式来传递属性及方法;Javascript 则是个基于原型(Prototype)的对语言 ,通过预先建立出的原型对象,当新对象建立时指定对象的原型要参照哪个原型对象。 而当我们调用对象的属性或方法时,若对象本身没有这个属性或方法,JavaScript 会自动寻找它原型中的方法,这也就是为什么可以直接调用 原型链你可能已经发现在前面的例子中, console.log(arr.__proto__) // Array 的 Prototypeconsole.log(arr.__proto__.__proto__) // Object 的 Prototypeconsole.log(arr.__proto__.__proto__.__proto__) // null 在上述机制中,每当对象建立时都会绑定原型,既然对象都有原型,对象原型本身也是对象,自然也不例外;由这个例子中我们可以看出:
由于每个对象都有原型,这样就形成了一个关联一个、层层相互依赖的从属关系,我们把它们叫做原型链(Prototype Chain) ;通过这种机制,让对象得以使用原型中的属性和方法,并凭借原型链一层一层的按顺序继承,让对象能拥有原型链上所有原型的功能,这就是 JavaScript 对象背后的运作机制。 补充:在 JavaScript 中,几乎每个原型链的末端都会是 Object,并最后指向到 使用原型说了这么多,该来点代码了,接下来就来练习一下原型的建立、设定及修改吧。 先来创建一个新的对象构造函数: 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: ƒ} 上面的例子创建了一个简单的对象构造函数 Q:为什么不直接用 原型继承接着再创建新的对象原型,并继承 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: ƒ} 这里建立了新的对象 Q:为什么需要重新设定 修改原型原型的引用、继承是直接参照到原型对象上,并非是在每个对象都复制一份原型;因此可以利用这个特性,在原型上增加自定义的属性和方法,让所有该类型的对象都能得到新方法;许多针对旧版浏览器的 Polyfill 就是这样实现的。 例如我们在写 Vue 项目的时候,可能都有做过类似的操作,把共用性高的属性方法放到 Object.defineProperty(Vue.prototype, '$date', { value: dateTimeFormat })// 之后就可以这样用vm.$date(dateObj) 这样的确很方便,但也要提醒开大家,当我们在做原型修改的时候要特别小心。接着刚才的例子,如果尝试对 Person.prototype.hello = function () { console.log(`Bye ${this.name}.`)}gary.hello() // Bye Gary.alice.hello() // Bye Alice. 如结果所示,当对象原型做修改时,所有原型链上有这个原型的对象,通通都会受到影响,不管对象是在修改前还是修改后创建的。 建议大家除非是 Polyfill,否则应该要极力避免对原生对象的原型进行修改,防止造成可能的意外结果。 ES6 的 Class看完前面这一大段,是不是觉得心很累?别担心,从 ES6 开始添加了 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 中关于对象原型的说明,希望能帮你理解对象原型,在这个什么都是对象的语言中,充分理解并掌握对象原型,是成为专业码农必须要突破的关卡之一。 |
|