这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
js 中的原型
大多数指南/教程通过直接进入“构造函数”开始解释 JavaScript 对象,我认为这是一个错误,因为它们很早就引入了一个相当复杂的概念,使 Javascript 从一开始就显得困难和混乱。让我们把这个留到以后。首先让我们从原型的基础开始。
原型链(又名原型继承)
Javascript 中的每个对象都有一个原型。当消息到达对象时,JavaScript 将首先尝试在该对象中查找属性,如果找不到,则将消息发送到对象的原型,依此类推。这就像基于类的语言中的单亲继承一样。
原型继承链可以随心所欲。但总的来说,制作长链并不是一个好主意,因为您的代码可能难以理解和维护。
proto 对象
要理解 JavaScript 中的原型链,没有什么比 proto 属性更简单了。不幸的是 proto 不是 JavaScript 标准接口的一部分,至少在 ES6 之前是这样。所以你不应该在生产代码中使用它。但无论如何,它使解释原型变得容易
//创建一个外来对象
var alien = {
kind: 'alien'
}
// 和person对象
var person = {
kind: 'person'
}
// 还有一个叫“zack”的东西
var zack = {};
// 指定alien作为zack的原型
zack.__proto__ = alien
//打印一下
console.log(zack.kind); //输出=> ‘alien’
// 指定person作为zack的原型
zack.__proto__ = person
// 再打印一下
console.log(zack.kind); //输出=> ‘person’
如你所见, proto 属性非常易于理解和使用。即使我们不应该在生产代码中使用 proto ,我认为这些示例为理解 JavaScript 对象模型提供了最好的基础。
您可以通过以下方式检查一个对象是否是另一个对象的原型:
console.log(alien.isPrototypeOf(zack)) //输出=> true
原型查找是动态的
您可以随时向对象的原型添加属性,原型链查找将按预期找到新属性:
var person = {}
var zack = {}
zack.__proto__ = person
// zack 此时对 kind 没有反应
console.log(zack.kind); //输出=> undefined
//但如果添加一下
person.kind = 'person'
//现在zack对kind做出了回应
// 因为person能找到kind
console.log(zack.kind); //输出=> 'person'
新的/更新的属性被分配给对象,而不是原型
如果更新原型中已经存在的属性会发生什么?让我们来看看:
var person = {
kind: 'person'
}
var zack = {}
zack.__proto__ = person
zack.kind = 'zack'
console.log(zack.kind); //输出=> 'zack'
// zack现在拥有“kind”的特性
console.log(person.kind); //输出=> 'person'
// person未被修改
请注意,属性“kind”现在同时存在于 person 和 zack 中。
对象.create
如前所述, proto 并不是一种将原型分配给对象的良好支持方式。所以下一个最简单的方法是使用Object.create() 。这在 ES5 中可用,但旧的浏览器/引擎可以使用这个es5-shim来填充。
var person = {
kind: 'person'
}
// person尚未被修改创建一个原型为person的新对象
var zack = Object.create(person);
console.log(zack.kind); //输出=> ‘person’
您可以将对象传递给 Object.create 为新对象添加特定属性
var zack = Object.create(person, {age: {value: 13} });
console.log(zack.age); //输出=> ‘13’
Object.getPrototype
您可以使用 Object.getPrototypeOf 获取对象的原型
var zack = Object.create(person);
Object.getPrototypeOf(zack); //输出=> person
没有 Object.setPrototype 这样的东西。
构造函数
构造函数是 JavaScript 中最常用的构造原型链的方式。构造函数的流行源于这样一个事实,即这是构造类型的唯一原始方式。许多引擎都针对构造函数进行了高度优化,这也是一个重要的考虑因素。
不幸的是,它们会让人感到困惑,在我看来,它们是新手发现 JavaScript 令人费解的主要原因之一,但它们是语言的重要组成部分,我们需要很好地理解它们。
function Foo () { }
var foo = new Foo();
//foo现在是foo的一个实例
console.log(foo instanceof Foo) //输出=> true
本质上,与关键字new一起使用的函数就像工厂一样,这意味着它们创建了新对象。他们创建的新对象通过其原型链接到函数,稍后会详细介绍。所以在 JavaScript 中我们称之为函数的实例。
'this' 是隐式分配的
当我们使用“ new ”时,JavaScript 会以“ this ”关键字的形式注入对正在创建的新对象的隐式引用。它还在函数末尾隐式返回此引用。
当我们这样做时:
function Foo () {
this.kind = 'foo'
}
var foo = new Foo();
foo.kind //输出=> 'foo'
在幕后,它就像在做这样的事情:
function Foo () {
var this = {}
// this是无效的,仅供举例说明
this.__proto__ = Foo.prototype;
this.kind = 'foo'
return this;
}
但请记住,隐含的“ this ”仅在使用“ new ”时分配给新对象。如果您忘记了“ new ”关键字,那么“ this ”将是全局对象。当然忘记new是导致多个错误的原因,所以不要忘记new。
我喜欢的一个约定是将函数的第一个字母大写,当它打算用作函数构造函数时,所以你现在直接发现你错过了new关键字。
“函数原型”
JavaScript 中的每个函数都有一个特殊的属性,称为“原型”。
function Foo(){ }
Foo.prototype
尽管听起来很混乱,但这个“原型”属性并不是函数的真正原型( proto )。
Foo.__proto__ === Foo.prototype //输出=> false
这当然会产生很多混淆,因为人们使用术语“原型”来指代不同的事物。我认为一个很好的说明是始终将函数的特殊“原型”属性称为“函数原型”,而不仅仅是“原型”。
' prototype ' 属性指向的对象将被指定为使用' new ' 时使用该函数创建的实例的原型。令人困惑?用一个例子更容易解释:
function Person (name) {
this.name = name;
}
// person 具有原型属性
// 我们可以向这个函数原型添加属性
Person.prototype.kind = 'person'
// 当我们使用new创建新对象时
var zack = new Person('Zack');
// 新物体的原型指向person。原型
zack.__proto__ == Person.prototype //=> true
// 在新对象中,我们可以访问亲自定义的属性。原型
zack.kind //输出=> person
这几乎就是关于 JavaScript 对象模型的所有知识。了解 proto 和function.prototype是如何相关的。