我不是那么喜欢动态编程语言,但我已经编写了相当一部分的 JavaScript 代码。我从来没有真正理解过这种基于原型的编程,有人知道它是如何工作的吗?
var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();
我记得前段时间与人们进行了很多讨论(我不确定自己在做什么),但据我所知,没有类的概念。它只是一个对象,这些对象的实例是原始对象的克隆,对吗?
但是 JavaScript 中这个“.prototype”属性的确切用途是什么?它与实例化对象有什么关系?
更新:正确的方法
var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!
function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK
这些slides也确实有很大帮助。
在 Java、C# 或 C++ 等实现经典继承的语言中,您首先创建一个类(对象的蓝图),然后您可以从该类创建新对象,或者您可以扩展该类,定义一个新类来增强原来的班级。
在 JavaScript 中,您首先创建一个对象(没有类的概念),然后您可以扩充自己的对象或从中创建新对象。对于习惯了经典方式的人来说,这并不难,但有点陌生且难以代谢。
例子:
//在 JavaScript 中定义一个函数对象来保存人员 var Person = function(name) { this.name = name; }; //向已经定义的对象动态添加一个新的getter Person.prototype.getName = function() { return this.name; }; //创建一个 Person 类型的新对象 var john = new Person("John"); //尝试getter alert(john.getName()); //如果现在我修改了人,约翰也会得到更新 Person.prototype.sayMyName = function() { alert('Hello, my name is ' + this.getName()); }; //调用john john.sayMyName()的新方法;
到目前为止,我一直在扩展基础对象,现在我创建另一个对象,然后从 Person 继承。
//Create a new object of type Customer by defining its constructor. It's not
//related to Person for now.
var Customer = function(name) {
this.name = name;
};
//Now I link the objects and to do so, we link the prototype of Customer to
//a new instance of Person. The prototype is the base that will be used to
//construct all new instances and also, will modify dynamically all already
//constructed objects because in JavaScript objects retain a pointer to the
//prototype
Customer.prototype = new Person();
//Now I can call the methods of Person on the Customer, let's try, first
//I need to create a Customer.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();
//If I add new methods to Person, they will be added to Customer, but if I
//add new methods to Customer they won't be added to Person. Example:
Customer.prototype.setAmountDue = function(amountDue) {
this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
return this.amountDue;
};
//Let's try:
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());
var Person = function (name) { this.name = name; }; Person.prototype.getName = function () { return this.name; }; var john = new Person("John");警报(约翰.getName()); Person.prototype.sayMyName = function () { alert('你好,我叫' + this.getName()); };约翰.sayMyName(); var Customer = function (name) { this.name = name; }; Customer.prototype = new Person(); var myCustomer = new Customer('Dream Inc.'); myCustomer.sayMyName(); Customer.prototype.setAmountDue = function (amountDue) { this.amountDue = amountDue; }; Customer.prototype.getAmountDue = function () { return this.amountDue; }; myCustomer.setAmountDue(2000);警报(myCustomer.getAmountDue());
虽然如前所述,我不能在 Person 上调用 setAmountDue()、getAmountDue()。
//The following statement generates an error.
john.setAmountDue(1000);
每个 JavaScript 对象 has an internal "slot" 称为 [[Prototype]]
,其值为 null
或 object
。您可以将插槽视为对象的属性,位于 JavaScript 引擎内部,对您编写的代码隐藏。 [[Prototype]]
周围的方括号是经过深思熟虑的,是表示内部槽的 ECMAScript 规范约定。
对象的 [[Prototype]]
所指向的值,俗称“该对象的原型”。
如果您通过点 (obj.propName
) 或方括号 (obj['propName']
) 表示法访问属性,并且对象没有直接具有这样的属性(即 自己的属性,可通过 {3 检查}),运行时会在 [[Prototype]]
引用的对象上查找具有该名称的属性。如果 [[Prototype]]
also 没有这样的属性,则依次检查其 [[Prototype]]
,依此类推。这样,原始对象的 原型链 会一直走下去,直到找到匹配项,或者到达其末端。原型链的顶部是 null
值。
现代 JavaScript 实现允许通过以下方式对 [[Prototype]]
进行读取和/或写入访问:
new 运算符(在构造函数返回的默认对象上配置原型链)、extends 关键字(使用类语法时配置原型链)、Object.create 将提供的参数设置为 [[Prototype]] 的生成的对象,Object.getPrototypeOf 和 Object.setPrototypeOf(在对象创建后获取/设置 [[Prototype]]),以及名为 __proto__ 的标准化访问器(即 getter/setter)属性(类似于 4.)
Object.getPrototypeOf
和 Object.setPrototypeOf
优于 __proto__
,部分原因是当对象的原型为 null
时 o.__proto__
is unusual 的行为。
对象的 [[Prototype]]
最初是在对象创建期间设置的。
如果您通过 new Func()
创建新对象,则默认情况下,该对象的 [[Prototype]]
将设置为 Func.prototype
引用的对象。
因此请注意,所有可与 new
运算符一起使用的类和函数,除了它们自己的 [[Prototype]]
内部槽外,还有一个名为 .prototype
的属性。这种双重用途“原型”一词是该语言新手无休止的困惑的根源。
将 new
与构造函数一起使用可以让我们模拟 JavaScript 中的经典继承;尽管 JavaScript 的继承系统——正如我们所见——是原型的,而不是基于类的。
在将类语法引入 JavaScript 之前,构造函数是模拟类的唯一方法。我们可以将构造函数的 .prototype
属性引用的对象的属性视为共享成员; IE。每个实例都相同的成员。在基于类的系统中,每个实例的方法都以相同的方式实现,因此方法在概念上被添加到 .prototype
属性中;但是,对象的字段是特定于实例的,因此在构造过程中会添加到对象本身。
如果没有类语法,开发人员必须手动配置原型链以实现与经典继承类似的功能。这导致了实现这一目标的不同方法的优势。
这是一种方法:
function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }
function inherit(child, parent) {
child.prototype = Object.create(parent.prototype)
child.prototype.constructor = child
return child;
}
Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'
...这是另一种方式:
function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }
function inherit(child, parent) {
function tmp() {}
tmp.prototype = parent.prototype
const proto = new tmp()
proto.constructor = child
child.prototype = proto
return child
}
Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'
ES2015 中引入的类语法通过提供 extends
作为“一种真正的方式”来配置原型链以模拟 JavaScript 中的经典继承,从而简化了事情。
因此,类似于上面的代码,如果您使用类语法创建一个新对象,如下所示:
class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'
...结果对象的 [[Prototype]]
将设置为 Parent
的实例,其 [[Prototype]]
又是 Parent.prototype
。
最后,如果您通过 Object.create(foo)
创建新对象,则生成的对象的 [[Prototype]]
将设置为 foo
。
这是一个非常简单的基于原型的对象模型,在解释过程中将被视为示例,尚无评论:
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
console.log(this.name);
}
var person = new Person("George");
在了解原型概念之前,我们必须考虑一些关键点。
1- JavaScript 函数的实际工作原理:
要迈出第一步,我们必须弄清楚 JavaScript 函数实际上是如何工作的,作为在其中使用 this
关键字的类函数,或者只是作为带有参数的常规函数,它的作用和它返回什么。
假设我们要创建一个 Person
对象模型。但在这一步中,我将尝试在不使用 prototype
和 new
关键字的情况下做同样的事情。
因此,在这一步中,functions
、objects
和 this
关键字就是我们所拥有的。
第一个问题是如何在不使用 new
关键字的情况下使用 this
关键字。
因此,要回答这个问题,假设我们有一个空对象,以及两个函数,例如:
var person = {};
function Person(name){ this.name = name; }
function getName(){
console.log(this.name);
}
现在不使用 new
关键字我们如何使用这些功能。所以 JavaScript 有 3 种不同的方法来做到这一点:
一个。第一种方法是将函数作为常规函数调用:
Person("George");
getName();//would print the "George" in the console
在这种情况下,这将是当前上下文对象,通常是浏览器中的全局 window
对象或 Node.js
中的 GLOBAL
。这意味着我们将拥有浏览器中的 window.name 或 Node.js 中的 GLOBAL.name,其值为“George”。
湾。我们可以将它们附加到一个对象上,作为它的属性
-最简单的方法是修改空的 person
对象,例如:
person.Person = Person;
person.getName = getName;
这样我们就可以这样称呼它们:
person.Person("George");
person.getName();// -->"George"
现在 person
对象就像:
Object {Person: function, getName: function, name: "George"}
-将属性附加到对象的另一种方法是使用该对象的 prototype
,该对象可以在任何名称为 __proto__
的 JavaScript 对象中找到,我已尝试对其进行解释关于摘要部分。所以我们可以通过这样做得到类似的结果:
person.__proto__.Person = Person;
person.__proto__.getName = getName;
但是这样我们实际上正在做的是修改 Object.prototype
,因为每当我们使用文字 ({ ... }
) 创建 JavaScript 对象时,它都是基于 Object.prototype
创建的,这意味着它作为一个名为 __proto__
的属性附加到新创建的对象,所以如果我们改变它,就像我们在之前的代码片段中所做的那样,所有的 JavaScript 对象都会被改变,这不是一个好的做法。那么现在更好的做法是什么:
person.__proto__ = {
Person: Person,
getName: getName
};
而现在其他的对象都安然无恙,但似乎还是不是一个好的做法。所以我们还有一个解决方案,但要使用这个解决方案,我们应该回到创建 person
对象的那一行代码 (var person = {};
),然后将其更改为:
var propertiesObject = {
Person: Person,
getName: getName
};
var person = Object.create(propertiesObject);
它所做的是创建一个新的 JavaScript Object
并将 propertiesObject
附加到 __proto__
属性。所以要确保你能做到:
console.log(person.__proto__===propertiesObject); //true
但这里的棘手点是您可以访问 person
对象第一级 __proto__
中定义的所有属性(阅读摘要部分了解更多详细信息)。
正如您所看到的,使用这两种方式中的任何一种 this
都会准确地指向 person
对象。
C。 JavaScript 有另一种方式为函数提供 this,即使用 call 或 apply 来调用函数。
apply() 方法使用给定的 this 值和作为数组(或类似数组的对象)提供的参数调用函数。
和
call() 方法使用给定的 this 值和单独提供的参数调用函数。
这种我最喜欢的方式,我们可以轻松地调用我们的函数,例如:
Person.call(person, "George");
或者
//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);
getName.call(person);
getName.apply(person);
这 3 种方法是找出 .prototype 功能的重要初始步骤。
2-新关键字如何工作?
这是了解 .prototype
功能的第二步。这是我用来模拟该过程的方法:
function Person(name){ this.name = name; }
my_person_prototype = { getName: function(){ console.log(this.name); } };
在这一部分中,我将尝试执行 JavaScript 所采取的所有步骤,而不使用 new
关键字和 prototype
,当您使用 new
关键字时。所以当我们做 new Person("George")
时,Person
函数作为构造函数,这些是 JavaScript 所做的,一一对应:
一个。首先,它创建一个空对象,基本上是一个空哈希,例如:
var newObject = {};
湾。 JavaScript 的下一步是将所有原型对象附加到新创建的对象
我们这里有 my_person_prototype
类似于原型对象。
for(var key in my_person_prototype){
newObject[key] = my_person_prototype[key];
}
这不是 JavaScript 实际附加原型中定义的属性的方式。实际方式与原型链概念有关。
一个。 & b。而不是这两个步骤,您可以通过执行以下操作获得完全相同的结果:
var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"
现在我们可以在 my_person_prototype
中调用 getName
函数:
newObject.getName();
C。然后它将该对象提供给构造函数,
我们可以用我们的样本来做到这一点,比如:
Person.call(newObject, "George");
或者
Person.apply(newObject, ["George"]);
然后构造函数可以做它想做的任何事情,因为构造函数内部的 this 是刚刚创建的对象。
现在是模拟其他步骤之前的最终结果: Object {name: "George"}
概括:
基本上,当您在函数上使用 new 关键字时,您正在调用该函数并且该函数用作构造函数,所以当您说:
new FunctionName()
JavaScript 在内部创建一个对象,一个空哈希,然后将该对象提供给构造函数,然后构造函数可以做任何它想做的事情,因为构造函数内部的 this 是刚刚创建的对象,然后如果您没有在函数中使用 return 语句,或者如果您在函数体的末尾放置了 return undefined;
,它当然会为您提供该对象。
所以当 JavaScript 在一个对象上查找一个属性时,它做的第一件事就是在那个对象上查找它。然后有一个秘密属性 [[prototype]]
,我们通常拥有它,就像 __proto__
一样,该属性是 JavaScript 接下来要研究的。而当它通过 __proto__
看时,只要它又是另一个 JavaScript 对象,它就有自己的 __proto__
属性,它会一直向上直到它到达下一个 __proto__
为空的点。要点是 JavaScript 中其 __proto__
属性为 null 的唯一对象是 Object.prototype
对象:
console.log(Object.prototype.__proto__===null);//true
这就是 JavaScript 中继承的工作原理。
https://i.stack.imgur.com/JnpBV.png
换句话说,当你在一个函数上有一个原型属性并且你在它上面调用一个 new 时,在 JavaScript 完成查看新创建的对象的属性之后,它会去查看函数的 .prototype
并且这也有可能对象有自己的内部原型。等等。
prototype
允许您创建课程。如果你不使用 prototype
那么它会变成一个静态的。
这是一个简短的例子。
var obj = new Object();
obj.test = function() { alert('Hello?'); };
在上述情况下,您有静态函数调用测试。此函数只能由 obj.test 访问,您可以将 obj 想象为一个类。
在下面的代码中
function obj()
{
}
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();
obj 已成为现在可以实例化的类。可以存在多个 obj 实例,并且它们都具有 test
函数。
以上是我的理解。我正在把它变成一个社区维基,所以如果我错了,人们可以纠正我。
prototype
是构造函数的属性,而不是实例,即您的代码是错误的!也许您的意思是对象的非标准属性 __proto__
,但那是完全不同的野兽......
原型之七公案
西罗桑在冥思之后下到火狐山上,心神清明平静。
然而,他的手却是不动声色,自己抓起一把毛笔,记下了下面的笔记。
0)两种不同的东西可以称为“原型”:
原型属性,如 obj.prototype
原型内部属性,在 ES5 中表示为 [[Prototype]]。它可以通过 ES5 Object.getPrototypeOf() 来检索。 Firefox 可以通过 __proto__ 属性作为扩展来访问它。 ES6 现在提到 __proto__ 的一些可选要求。
1)这些概念的存在是为了回答这个问题:
当我做obj.property时,JS在哪里寻找.property?
直观地说,经典继承应该会影响属性查找。
2)
__proto__ 用于点。在 obj.property 中进行属性查找。
.prototype 不直接用于查找,仅用于间接查找,因为它在使用 new 创建对象时确定 __proto__。
查找顺序为:
使用 obj.p = ... 或 Object.defineProperty(obj, ...) 添加的 obj 属性
obj.__proto__ 的属性
obj.__proto__.__proto__ 的属性等等
如果某些 __proto__ 为 null,则返回 undefined。
这就是所谓的原型链。
您可以避免使用 obj.hasOwnProperty('key')
和 Object.getOwnPropertyNames(f)
进行 .
查找
3) 设置 obj.__proto__
的方法主要有两种:
new: var F = function() {} var f = new F() then new 已设置: f.__proto__ === F.prototype 这是使用 .prototype 的地方。
Object.create:f = Object.create(proto) 设置:f.__proto__ === proto
4)代码:
var F = function(i) { this.i = i }
var f = new F(1)
对应于下图(省略了一些 Number
内容):
(Function) ( F ) (f)----->(1)
| ^ | | ^ | i |
| | | | | | |
| | | | +-------------------------+ | |
| |constructor | | | | |
| | | +--------------+ | | |
| | | | | | |
| | | | | | |
|[[Prototype]] |[[Prototype]] |prototype |constructor |[[Prototype]]
| | | | | | |
| | | | | | |
| | | | +----------+ | |
| | | | | | |
| | | | | +-----------------------+ |
| | | | | | |
v | v v | v |
(Function.prototype) (F.prototype) |
| | |
| | |
|[[Prototype]] |[[Prototype]] [[Prototype]]|
| | |
| | |
| +-------------------------------+ |
| | |
v v v
(Object.prototype) (Number.prototype)
| | ^
| | |
| | +---------------------------+
| | |
| +--------------+ |
| | |
| | |
|[[Prototype]] |constructor |prototype
| | |
| | |
| | -------------+
| | |
v v |
(null) (Object)
此图显示了许多语言预定义的对象节点:
无效的
目的
对象.原型
功能
函数原型
1
Number.prototype(可以通过 (1).__proto__ 找到,括号必须满足语法)
我们的 2 行代码只创建了以下新对象:
F
F
F.原型
i
现在是 f
的属性,因为当您这样做时:
var f = new F(1)
它评估 F
,其中 this
是 new
将返回的值,然后将其分配给 f
。
5) .constructor
通常来自 F.prototype
通过 .
查找:
f.constructor === F
!f.hasOwnProperty('constructor')
Object.getPrototypeOf(f) === F.prototype
F.prototype.hasOwnProperty('constructor')
F.prototype.constructor === f.constructor
当我们编写 f.constructor
时,JavaScript 将 .
查找为:
f 没有 .constructor
f.__proto__ === F.prototype 有 .constructor === F,所以拿它
结果 f.constructor == F
直观上是正确的,因为 F
用于构造 f
,例如设置字段,很像经典的 OOP 语言。
6)经典的继承语法可以通过操作原型链来实现。
ES6 添加了 class
和 extends
关键字,它们主要是以前可能的原型操作疯狂的语法糖。
class C {
constructor(i) {
this.i = i
}
inc() {
return this.i + 1
}
}
class D extends C {
constructor(i) {
super(i)
}
inc2() {
return this.i + 2
}
}
// Inheritance syntax works as expected.
c = new C(1)
c.inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// http://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined
没有所有预定义对象的简化图:
(c)----->(1)
| i
|
|
|[[Prototype]]
|
|
v __proto__
(C)<--------------(D) (d)
| | | |
| | | |
| |prototype |prototype |[[Prototype]]
| | | |
| | | |
| | | +---------+
| | | |
| | | |
| | v v
|[[Prototype]] (D.prototype)--------> (inc2 function object)
| | | inc2
| | |
| | |[[Prototype]]
| | |
| | |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)------->(inc function object)
| inc
v
Function.prototype
让我们花点时间研究一下以下是如何工作的:
c = new C(1)
c.inc() === 2
第一行将 c.i
设置为 1
,如“4)”中所述。
在第二行,当我们这样做时:
c.inc()
.inc 是通过 [[Prototype]] 链找到的:c -> C -> C.prototype -> inc
当我们在 Javascript 中将函数调用为 XY() 时,JavaScript 会在 Y() 函数调用中自动将其设置为等于 X!
完全相同的逻辑也解释了 d.inc
和 d.inc2
。
这篇文章https://javascript.info/class#not-just-a-syntax-sugar提到了值得了解的class
的进一步影响。如果没有 class
关键字,其中一些可能无法实现(TODO 检查哪个):
[[FunctionKind]]:"classConstructor",强制用new调用构造函数:ES6类构造函数不能作为普通函数调用的原因是什么?
类方法是不可枚举的。可以使用 Object.defineProperty 来完成。
类总是使用严格的。可以通过对每个函数显式使用严格来完成,这无疑是乏味的。
读完这个帖子后,我对 JavaScript Prototype Chain 感到困惑,然后我找到了这些图表
https://i.stack.imgur.com/rcGmc.png
这是一个通过原型链显示 JavaScript 继承的清晰图表
和
http://www.javascriptbank.com/javascript/article/JavaScript_Classical_Inheritance/
这个包含一个带有代码的示例和几个漂亮的图表。
原型链最终回退到 Object.prototype。原型链可以在技术上随心所欲地扩展,每次通过将子类的原型设置为父类的对象。
希望对你理解 JavaScript Prototype Chain 也有帮助。
每个对象都有一个内部属性 [[Prototype]],将它链接到另一个对象:
object [[Prototype]] → anotherObject
在传统 javascript 中,链接对象是函数的 prototype
属性:
object [[Prototype]] → aFunction.prototype
一些环境将 [[Prototype]] 公开为 __proto__
:
anObject.__proto__ === anotherObject
在创建对象时创建 [[Prototype]] 链接。
// (1) Object.create:
var object = Object.create(anotherObject)
// object.__proto__ = anotherObject
// (2) ES6 object initializer:
var object = { __proto__: anotherObject };
// object.__proto__ = anotherObject
// (3) Traditional JavaScript:
var object = new aFunction;
// object.__proto__ = aFunction.prototype
所以这些语句是等价的:
var object = Object.create(Object.prototype);
var object = { __proto__: Object.prototype }; // ES6 only
var object = new Object;
您实际上无法在 new 语句中看到链接目标 (Object.prototype
);相反,目标是由构造函数(Object
)隐含的。
记住:
每个对象都有一个链接,[[Prototype]],有时暴露为 __proto__。
每个函数都有一个原型属性,最初持有一个空对象。
使用 new 创建的对象链接到其构造函数的原型属性。
如果一个函数从不用作构造函数,它的原型属性将不被使用。
如果您不需要构造函数,请使用 Object.create 而不是 new。
Object.create()
docs,@sam。到 __proto__
和 Object.prototype
的链接将是很好的增强。我喜欢你关于原型如何与构造函数和 Object.create()
一起工作的示例,但它们可能是你想要摆脱的冗长且不太相关的部分。
Javascript没有通常意义上的继承,但它有原型链。
原型链
如果在对象中找不到对象的成员,它会在原型链中查找它。链由其他对象组成。可以使用 __proto__
变量访问给定实例的原型。每个对象都有一个,因为 javascript 中的类和实例之间没有区别。
将函数/变量添加到原型的优点是它必须只在内存中一次,而不是每个实例。
它对于继承也很有用,因为原型链可以包含许多其他对象。
这篇文章很长。但我相信它会清除您关于 JavaScript 继承的“原型”性质的大部分疑问。甚至更多。请阅读完整的文章。
JavaScript 基本上有两种数据类型
非对象
对象
非对象
以下是非对象数据类型
细绳
数(包括 NaN 和 Infinity)
布尔值(真,假)
不明确的
当您使用 typeof 运算符时,这些数据类型会返回以下内容
typeof "字符串文字" (或包含字符串文字的变量) === 'string'
typeof 5(或任何数字文字或包含数字文字或 NaN 或 Infynity 的变量)=== 'number'
typeof true (或 false 或包含 true 或 false 的变量) === 'boolean'
typeof undefined(或未定义的变量或包含未定义的变量)=== 'undefined'
字符串、数字和布尔数据类型可以表示为对象和非对象。当它们表示为对象时,它们的 typeof 始终为 === 'object'。一旦我们了解了对象数据类型,我们将回到这一点。
对象
对象数据类型可以进一步分为两种类型
函数类型对象 非函数类型对象
Function 类型对象是使用 typeof 运算符返回字符串 'function' 的对象。所有用户定义的函数和所有可以使用 new 运算符创建新对象的 JavaScript 内置对象都属于这一类。例如。
目的
细绳
数字
布尔值
大批
类型化数组
正则表达式
功能
所有其他可以使用 new 运算符创建新对象的内置对象
function UserDefinedFunction(){ /*用户定义代码 */ }
所以,typeof(Object) === typeof(String) === typeof(Number) === typeof(Boolean) === typeof(Array) === typeof(RegExp) === typeof(Function) == = typeof(UserDefinedFunction) === '函数'
所有的 Function 类型对象实际上都是内置 JavaScript 对象 Function 的实例(包括 Function 对象,即它是递归定义的)。就好像这些对象已按以下方式定义
var Object= new Function ([native code for object Object])
var String= new Function ([native code for object String])
var Number= new Function ([native code for object Number])
var Boolean= new Function ([native code for object Boolean])
var Array= new Function ([native code for object Array])
var RegExp= new Function ([native code for object RegExp])
var Function= new Function ([native code for object Function])
var UserDefinedFunction= new Function ("user defined code")
如前所述,Function 类型对象可以使用 new 运算符进一步创建新对象。例如,可以使用 Object、String、Number、Boolean、Array、RegExp 或 UserDefinedFunction 类型的对象创建
var a=new Object() or var a=Object() or var a={} //Create object of type Object
var a=new String() //Create object of type String
var a=new Number() //Create object of type Number
var a=new Boolean() //Create object of type Boolean
var a=new Array() or var a=Array() or var a=[] //Create object of type Array
var a=new RegExp() or var a=RegExp() //Create object of type RegExp
var a=new UserDefinedFunction()
这样创建的对象都是非函数类型的对象,并返回它们的 typeof==='object'。在所有这些情况下,对象“a”不能使用 operator new 进一步创建对象。所以以下是错误的
var b=new a() //error. a is not typeof==='function'
内置对象 Math 是 typeof==='object'。因此,新运算符不能创建 Math 类型的新对象。
var b=new Math() //error. Math is not typeof==='function'
另请注意,Object、Array 和 RegExp 函数甚至可以在不使用 operator new 的情况下创建新对象。然而后面的没有。
var a=String() // Create a new Non Object string. returns a typeof==='string'
var a=Number() // Create a new Non Object Number. returns a typeof==='number'
var a=Boolean() //Create a new Non Object Boolean. returns a typeof==='boolean'
用户定义的函数是特殊情况。
var a=UserDefinedFunction() //may or may not create an object of type UserDefinedFunction() based on how it is defined.
由于 Function 类型的对象可以创建新对象,因此它们也称为构造函数。
每个构造函数/函数(无论是内置的还是用户定义的)在自动定义时都有一个名为“原型”的属性,其值默认设置为对象。该对象本身有一个名为“constructor”的属性,默认情况下它引用回 Constructor/Function 。
例如当我们定义一个函数时
function UserDefinedFunction()
{
}
以下自动发生
UserDefinedFunction.prototype={constructor:UserDefinedFunction}
这个“原型”属性只存在于函数类型对象中(而不存在于非函数类型对象中)。
这是因为当一个新对象被创建(使用 new 操作符)时,它会从构造函数的当前原型对象继承所有属性和方法,即在新创建的对象中创建一个内部引用,该对象引用构造函数的当前原型对象所引用的对象。
在对象中创建的用于引用继承属性的“内部引用”称为对象的原型(它引用了构造函数的“原型”属性所引用的对象,但与它不同)。对于任何对象(函数或非函数),可以使用 Object.getPrototypeOf() 方法检索。使用这种方法可以追踪对象的原型链。
此外,创建的每个对象(函数类型或非函数类型)都有一个“构造函数”属性,该属性继承自构造函数的原型属性引用的对象。默认情况下,此“构造函数”属性引用创建它的构造函数(如果构造函数的默认“原型”未更改)。
对于所有 Function 类型对象,构造函数始终是 function Function(){}
对于非函数类型对象(例如 Javascript Built in Math 对象),构造函数是创建它的函数。对于 Math 对象,它是函数 Object(){}。
如果没有任何支持代码,上面解释的所有概念可能有点难以理解。请逐行阅读以下代码以了解该概念。尝试执行它以获得更好的理解。
function UserDefinedFunction()
{
}
/* creating the above function automatically does the following as mentioned earlier
UserDefinedFunction.prototype={constructor:UserDefinedFunction}
*/
var newObj_1=new UserDefinedFunction()
alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype) //Displays true
alert(newObj_1.constructor) //Displays function UserDefinedFunction
//Create a new property in UserDefinedFunction.prototype object
UserDefinedFunction.prototype.TestProperty="test"
alert(newObj_1.TestProperty) //Displays "test"
alert(Object.getPrototypeOf(newObj_1).TestProperty)// Displays "test"
//Create a new Object
var objA = {
property1 : "Property1",
constructor:Array
}
//assign a new object to UserDefinedFunction.prototype
UserDefinedFunction.prototype=objA
alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype) //Displays false. The object referenced by UserDefinedFunction.prototype has changed
//The internal reference does not change
alert(newObj_1.constructor) // This shall still Display function UserDefinedFunction
alert(newObj_1.TestProperty) //This shall still Display "test"
alert(Object.getPrototypeOf(newObj_1).TestProperty) //This shall still Display "test"
//Create another object of type UserDefinedFunction
var newObj_2= new UserDefinedFunction();
alert(Object.getPrototypeOf(newObj_2)===objA) //Displays true.
alert(newObj_2.constructor) //Displays function Array()
alert(newObj_2.property1) //Displays "Property1"
alert(Object.getPrototypeOf(newObj_2).property1) //Displays "Property1"
//Create a new property in objA
objA.property2="property2"
alert(objA.property2) //Displays "Property2"
alert(UserDefinedFunction.prototype.property2) //Displays "Property2"
alert(newObj_2.property2) // Displays Property2
alert(Object.getPrototypeOf(newObj_2).property2) //Displays "Property2"
每个对象的原型链最终都追溯到 Object.prototype(它本身没有任何原型对象)。以下代码可用于跟踪对象的原型链
var o=Starting object;
do {
alert(o + "\n" + Object.getOwnPropertyNames(o))
}while(o=Object.getPrototypeOf(o))
各种对象的原型链如下所示。
每个 Function 对象(包括内置的 Function 对象)-> Function.prototype -> Object.prototype -> null
简单对象(由 new Object() 或 {} 创建,包括内置的 Math 对象)-> Object.prototype -> null
使用 new 或 Object.create 创建的对象 -> 一个或多个原型链 -> Object.prototype -> null
要创建没有任何原型的对象,请使用以下命令:
var o=Object.create(null)
alert(Object.getPrototypeOf(o)) //Displays null
有人可能认为将 Constructor 的原型属性设置为 null 会创建一个具有 null 原型的对象。然而,在这种情况下,新创建的对象的原型设置为 Object.prototype,其构造函数设置为函数 Object。下面的代码证明了这一点
function UserDefinedFunction(){}
UserDefinedFunction.prototype=null// Can be set to any non object value (number,string,undefined etc.)
var o=new UserDefinedFunction()
alert(Object.getPrototypeOf(o)==Object.prototype) //Displays true
alert(o.constructor) //Displays Function Object
以下是本文的摘要
对象有函数类型和非函数类型两种
只有 Function 类型的对象可以使用 new 运算符创建新对象。这样创建的对象是非函数类型的对象。 Non Function 类型的对象不能使用 operator new 进一步创建对象。
默认情况下,所有函数类型对象都有一个“原型”属性。这个“原型”属性引用了一个具有“构造函数”属性的对象,该属性默认引用了函数类型对象本身。
所有对象(函数类型和非函数类型)都有一个“构造函数”属性,默认情况下引用创建它的函数类型对象/构造函数。
在内部创建的每个对象都引用由创建它的构造函数的“原型”属性引用的对象。这个对象被称为创建对象的原型(它不同于它所引用的函数类型对象的“原型”属性)。这样创建的对象可以直接访问构造函数的“原型”属性(在对象创建时)引用的对象中定义的方法和属性。
可以使用 Object.getPrototypeOf() 方法检索对象的原型(以及继承的属性名称)。事实上,这种方法可以用于导航对象的整个原型链。
每个对象的原型链最终都追溯到 Object.prototype(除非对象是使用 Object.create(null) 创建的,在这种情况下对象没有原型)。
typeof(new Array())==='object' 是语言设计的,而不是 Douglas Crockford 指出的错误
将构造函数的原型属性设置为 null(或 undefined,number,true,false,string)不应创建具有 null 原型的对象。在这种情况下,新创建的对象的原型设置为 Object.prototype,其构造函数设置为函数 Object。
希望这可以帮助。
将原型链分为两类可能会有所帮助。
考虑构造函数:
function Person() {}
Object.getPrototypeOf(Person)
的值是一个函数。实际上是Function.prototype
。由于 Person
是作为函数创建的,因此它与所有函数共享相同的原型函数对象。它与 Person.__proto__
相同,但不应使用该属性。无论如何,使用 Object.getPrototypeOf(Person)
,您可以有效地爬上所谓的原型链的阶梯。
向上的链条如下所示:
Person
→ Function.prototype
→ Object.prototype
(终点)
重要的是,此原型链与 Person
可以构造的对象几乎没有关系。这些构造的对象有自己的原型链,并且这个链可能没有与上面提到的相同的近亲。
以这个对象为例:
var p = new Person();
p 与 Person 没有直接的原型链关系。他们的关系是不同的。 p 对象有自己的原型链。使用 Object.getPrototypeOf
,您会发现链如下:
p
→ Person.prototype
→ Object.prototype
(终点)
此链中没有函数对象(尽管可能如此)。
所以 Person
似乎与两种链条有关,它们过着自己的生活。要从一条链“跳转”到另一条链,请使用:
.prototype:从构造函数链跳转到创建对象链。因此,此属性仅针对函数对象定义(因为 new 只能用于函数)。 .constructor:从创建对象的链跳转到构造函数的链。
这是所涉及的两个原型链的可视化演示,以列表示:
https://i.stack.imgur.com/FPPdI.png
总结一下:
原型属性不提供主体原型链的信息,而是主体创建的对象的信息。
毫不奇怪,属性 prototype
的名称会导致混淆。如果将该属性命名为 prototypeOfConstructedInstances
或类似的名称,可能会更清楚。
你可以在两个原型链之间来回跳转:
Person.prototype.constructor === Person
通过将不同的对象显式分配给 prototype
属性可以打破这种对称性(稍后会详细介绍)。
创建一个函数,获取两个对象
Person.prototype
是在创建函数 Person
的同时创建的对象。它有 Person
作为构造函数,即使该构造函数实际上还没有执行。所以同时创建了两个对象:
函数 Person 本身 当函数被调用为构造函数时将充当原型的对象
两者都是对象,但它们具有不同的作用:函数对象构造,而另一个对象代表函数将构造的任何对象的原型。原型对象将成为其原型链中构造对象的父对象。
由于函数也是一个对象,它在自己的原型链中也有自己的父级,但请记住,这两个链是关于不同事物的。
以下是一些有助于理解问题的等式 - 所有这些都打印 true
:
函数人(){}; // 这是构造函数(函数对象)的原型链信息:console.log(Object.getPrototypeOf(Person) === Function.prototype); // 在同一层次结构中更进一步:console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype); console.log(Object.getPrototypeOf(Object.prototype) === null); console.log(Person.__proto__ === Function.prototype); // 这里我们换车道,看构造函数console.log(Person.constructor === Function); console.log(Person instanceof Function); // Person.prototype 是由 Person 创建的(在创建时) // 这里我们来回交换通道:console.log(Person.prototype.constructor === Person); // 虽然它不是它的实例:console.log(!(Person.prototype instanceof Person)); // 实例是由构造函数创建的对象: var p = new Person(); // 与为构造函数显示的内容类似,这里我们对构造函数创建的对象具有 // 相同的内容:console.log(Object.getPrototypeOf(p) === Person.prototype); console.log(p.__proto__ === Person.prototype); // 这里我们交换通道,并查看构造函数 console.log(p.constructor === Person); console.log(p instanceof Person);
向原型链添加关卡
尽管在创建构造函数时会创建原型对象,但您可以忽略该对象,并分配另一个对象,该对象应用作该构造函数创建的任何后续实例的原型。
例如:
function Thief() { }
var p = new Person();
Thief.prototype = p; // this determines the prototype for any new Thief objects:
var t = new Thief();
现在 t 的原型链比 p 的原型链长一步:
t
→ p
→ Person.prototype
→ Object.prototype
(终点)
另一个原型链不再:Thief
和 Person
是在其原型链中共享同一个父级的兄弟:
Person
}
Thief
} → Function.prototype
→ Object.prototype
(终点)
然后可以将之前显示的图形扩展到此(原始 Thief.prototype
被省略):
https://i.stack.imgur.com/m5DXc.png
蓝线代表原型链,其他彩色线代表其他关系:
对象与其构造函数之间
在构造函数和将用于构造对象的原型对象之间
prototypal
继承的概念对于许多开发人员来说是最复杂的概念之一。让我们尝试了解问题的根源以更好地理解 prototypal inheritance
。让我们从 plain
函数开始。
https://i.stack.imgur.com/BsHT0.png
如果我们在 Tree function
上使用 new
运算符,我们将其称为 constructor
函数。
https://i.stack.imgur.com/cU6Qh.png
每个 JavaScript
函数都有一个 prototype
。当您记录 Tree.prototype
时,您会得到...
https://i.stack.imgur.com/Xop8c.png
如果您查看上面的 console.log()
输出,您可以看到 Tree.prototype
上的构造函数属性和 __proto__
属性。 __proto__
表示此 function
所基于的 prototype
,由于这只是一个没有设置 inheritance
的普通 JavaScript function
,它指的是刚刚内置的 Object prototype
到 JavaScript...
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype
这有 .toString, .toValue, .hasOwnProperty
之类的东西......
我的 mozilla 带来的 __proto__
已弃用,取而代之的是 Object.getPrototypeOf
方法以获取 object's prototype
。
https://i.stack.imgur.com/GtcJO.png
Object.getPrototypeOf(Tree.prototype); // Object {}
让我们向 Tree
prototype
添加一个方法。
https://i.stack.imgur.com/BsHT0.png
我们已修改 Root
并向其添加了 function
分支。
https://i.stack.imgur.com/cU6Qh.png
这意味着当您创建 Tree
的 instance
时,您可以调用它的 branch
方法。
https://i.stack.imgur.com/Xop8c.png
我们还可以将 primitives
或 objects
添加到 Prototype
。
https://i.stack.imgur.com/GtcJO.png
让我们将 child-tree
添加到我们的 Tree
。
https://i.stack.imgur.com/ggFON.png
这里 Child
从 Tree 继承了它的 prototype
,我们在这里所做的是使用 Object.create()
方法根据您传递的内容创建一个新对象,这里是 Tree.prototype
。在这种情况下,我们所做的是将 Child 的原型设置为一个看起来与 Tree
原型相同的新对象。接下来我们设置 Child's constructor to Child
,如果我们不这样做,它将指向 Tree()
。
https://i.stack.imgur.com/yiZcY.png
Child
现在有自己的 prototype
,它的 __proto__
指向 Tree
,Tree's prototype
指向基 Object
。
Child
|
\
\
Tree.prototype
- branch
|
|
\
\
Object.prototype
-toString
-valueOf
-etc., etc.
现在您创建一个 Child
的 instance
并调用最初在 Tree
中可用的 branch
。我们实际上还没有在 Child prototype
上定义我们的 branch
。但是,在 Child 继承的 Root prototype
中。
https://i.stack.imgur.com/k6BNb.png
在 JS 中,一切都不是对象,一切都可以像对象一样。
Javascript
有像 strings, number, booleans, undefined, null.
这样的原语它们不是 object(i.e reference types)
,但肯定可以像 object
一样工作。让我们看一个例子。
https://i.stack.imgur.com/WVjiv.png
在此清单的第一行中,将 primitive
字符串值分配给 name。第二行将 name 视为 object
并使用点表示法调用 charAt(0)
。
这就是幕后发生的事情: // JavaScript
引擎的作用
https://i.stack.imgur.com/l6MHc.png
String object
在被销毁之前仅存在于一个语句(称为 autoboxing
的过程)。让我们再次回到我们的 prototypal
inheritance
。
Javascript 支持通过基于原型的委托进行继承。
每个函数都有一个原型属性,它引用另一个对象。
从对象本身或通过原型链查找属性/函数(如果它不存在)
JS 中的 prototype
是一个对象,它 yields
指向另一个 object
的父级。 [ie..delegation] Delegation
表示如果您不能做某事,您会告诉其他人为您做这件事。
https://i.stack.imgur.com/W0NUA.png
https://jsfiddle.net/say0tzpL/1/
如果您查看上面的小提琴,dog 可以访问 toString
方法,但它在其中不可用,但可以通过委托给 Object.prototype
的原型链获得
https://i.stack.imgur.com/lWILf.png
如果您查看下面的内容,我们正在尝试访问每个 function
中都可用的 call
方法。
https://i.stack.imgur.com/iF4RN.png
https://jsfiddle.net/rknffckc/
如果您查看上面的小提琴,Profile
函数可以访问 call
方法,但它在其中不可用,但可以通过委托给 Function.prototype
的原型链获得
https://i.stack.imgur.com/Mijkj.png
注意: prototype
是函数构造函数的属性,而 __proto__
是从函数构造函数构造的对象的属性。每个函数都带有一个 prototype
属性,其值为空 object
。当我们创建函数的实例时,我们会得到一个内部属性 [[Prototype]]
或 __proto__
,其引用是函数 constructor
的原型。
https://i.stack.imgur.com/HvzDP.png
上图看起来有点复杂,但可以全面了解 prototype chaining
的工作原理。让我们慢慢来看看:
有两个实例 b1
和 b2
,其构造函数是 Bar
,父对象是 Foo,并且通过 Bar
和 Foo
具有来自原型链 identify
和 speak
的两个方法
https://i.stack.imgur.com/EllEL.png
https://jsfiddle.net/kbp7jr7n/
如果您查看上面的代码,我们有 Foo
具有方法 identify()
的构造函数和具有 speak
方法的 Bar
构造函数。我们创建两个 Bar
实例 b1
和 b2
,其父类型为 Foo
。现在在调用 Bar
的 speak
方法时,我们可以通过 prototype
链识别谁在调用说话。
https://i.stack.imgur.com/V7fH7.png
Bar
现在具有在其 prototype
中定义的 Foo
的所有方法。让我们进一步深入了解 Object.prototype
和 Function.prototype
以及它们之间的关系。如果查找 Foo
的构造函数,Bar
和 Object
是 Function constructor
。
https://i.stack.imgur.com/wzzRu.png
Bar
的 prototype
是 Foo
,Foo
的 prototype
是 Object
,如果仔细观察,Foo
的 prototype
与 Object.prototype
相关。
https://i.stack.imgur.com/wEOxo.png
在我们关闭它之前,让我们在这里用一小段代码来总结以上所有内容。我们在这里使用 instanceof
运算符来检查 object
在其 prototype
链中是否具有 constructor
的 prototype
属性,该属性在下面总结了整个大图。
https://i.stack.imgur.com/n84uV.png
我希望这个添加的一些信息,我知道这可能很容易掌握......简而言之,它只是链接到对象的对象!!!!
Child now has its own prototype, its __proto__ points to Tree
- 似乎是错误的。 __proto__
指向 Function.prototype
而不是 Tree
。
这个“.prototype”属性的确切目的是什么?
标准类的接口变得可扩展。例如,您正在使用 Array
类,并且您还需要为所有数组对象添加自定义序列化程序。您会花时间编写子类,还是使用组合或......原型属性通过让用户控制类可用的成员/方法的确切集合来解决这个问题。
将原型视为一个额外的 vtable 指针。当原始类中缺少某些成员时,会在运行时查找原型。
https://i.stack.imgur.com/Vf4qR.jpg
我发现在引用 obj_n.prop_X
时将“原型链”解释为递归约定很有帮助:
如果 obj_n.prop_X
不存在,请检查 obj_n+1.prop_X
其中 obj_n+1 = obj_n.[[prototype]]
如果 prop_X
最终在第 k 个原型对象中找到,则
obj_1.prop_X = obj_1.[[prototype]].[[prototype]]..(k-times)..[[prototype]].prop_X
您可以在此处通过属性找到 Javascript 对象的关系图:
https://i.stack.imgur.com/2tGyY.jpg
当构造函数创建一个对象时,该对象隐式引用构造函数的“原型”属性以解析属性引用。构造函数的“原型”属性可以被程序表达式constructor.prototype引用,并且添加到对象原型的属性通过继承被共享原型的所有对象共享。
这里有两个不同但相关的实体需要解释:
函数的 .prototype 属性。
所有对象 [2] 的 [[Prototype]][1] 属性。
这是两个不同的东西。
[[原型]] 属性:
这是存在于所有 [2] 对象上的属性。
这里存储的是另一个对象,作为一个对象本身,它有自己的 [[Prototype]]
指向另一个对象。该其他对象有自己的 [[Prototype]]
。这个故事一直持续到您到达提供可在所有对象上访问的方法的原型对象(如 .toString
)。
[[Prototype]]
属性是构成 [[Prototype]]
链的一部分。例如,当对对象执行 [[Get]]
或 [[Set]]
操作时,会检查此 [[Prototype]]
对象链:
var obj = {}
obj.a // [[Get]] consults prototype chain
obj.b = 20 // [[Set]] consults prototype chain
.prototype 属性:
这是一个仅在函数上才有的属性。使用一个非常简单的函数:
function Bar(){};
.prototype
属性包含一个对象,当您执行 var b = new Bar
时将分配给 b.[[Prototype]]
。你可以很容易地检查这个:
// Both assign Bar.prototype to b1/b2[[Prototype]]
var b = new Bar;
// Object.getPrototypeOf grabs the objects [[Prototype]]
console.log(Object.getPrototypeOf(b) === Bar.prototype) // true
最重要的 .prototype
之一是 of the Object
function。此原型包含所有 [[Prototype]]
链包含的原型对象。在它上面,定义了新对象的所有可用方法:
// Get properties that are defined on this object
console.log(Object.getOwnPropertyDescriptors(Object.prototype))
现在,由于 .prototype
是一个对象,它有一个 [[Prototype]]
属性。当您不对 Function.prototype
进行任何分配时,.prototype
的 [[Prototype]]
指向原型对象 (Object.prototype
)。每当您创建新函数时,都会自动执行此操作。
这样,每当您执行 new Bar;
时,原型链就会为您设置好,您将获得在 Bar.prototype
上定义的所有内容以及在 Object.prototype
上定义的所有内容:
var b = new Bar;
// Get all Bar.prototype properties
console.log(b.__proto__ === Bar.prototype)
// Get all Object.prototype properties
console.log(b.__proto__.__proto__ === Object.prototype)
当您做向 Function.prototype
赋值时,您所做的只是扩展原型链以包含另一个对象。这就像在单链表中的插入。
这基本上改变了 [[Prototype]]
链,允许在分配给 Function.prototype
的对象上定义的属性被该函数创建的任何对象看到。
[1:这不会让任何人感到困惑;在许多实现中通过 the __proto__
property 提供。
[2]:除 null
之外的所有内容。
让我告诉你我对原型的理解。我不打算将这里的继承与其他语言进行比较。我希望人们停止比较语言,而只理解语言本身。了解原型和原型继承是如此简单,我将在下面向您展示。
原型就像一个模型,您可以根据它创建产品。要理解的关键点是,当您使用另一个对象作为原型创建对象时,原型和产品之间的联系是永恒的。例如:
var model = {x:2};
var product = Object.create(model);
model.y = 5;
product.y
=>5
每个对象都包含一个称为 [[prototype]] 的内部属性,可以通过 Object.getPrototypeOf()
函数访问。 Object.create(model)
创建一个新对象并将其 [[prototype]] 属性设置为对象 model。因此,当您执行 Object.getPrototypeOf(product)
时,您将获得对象 model。
产品中的属性按以下方式处理:
当访问一个属性以读取它的值时,它会在作用域链中查找。对变量的搜索从产品向上开始到它的原型。如果在搜索中找到这样的变量,则搜索将在此处停止,并返回该值。如果在作用域链中找不到这样的变量,则返回 undefined。
写入(更改)属性时,始终将属性写入产品对象。如果产品还没有这样的属性,它会被隐式创建和写入。
这种使用原型属性的对象链接称为原型继承。就这么简单,同意吗?
考虑以下 keyValueStore
对象:
var keyValueStore = (function() {
var count = 0;
var kvs = function() {
count++;
this.data = {};
this.get = function(key) { return this.data[key]; };
this.set = function(key, value) { this.data[key] = value; };
this.delete = function(key) { delete this.data[key]; };
this.getLength = function() {
var l = 0;
for (p in this.data) l++;
return l;
}
};
return { // Singleton public properties
'create' : function() { return new kvs(); },
'count' : function() { return count; }
};
})();
我可以通过这样做来创建这个对象的一个新实例:
kvs = keyValueStore.create();
此对象的每个实例都将具有以下公共属性:
数据
得到
放
删除
获取长度
现在,假设我们创建了这个 keyValueStore
对象的 100 个实例。尽管 get
、set
、delete
、getLength
将对这 100 个实例中的每一个执行完全相同的操作,但每个实例都有自己的此函数副本。
现在,想象一下,如果您只有一个 get
、set
、delete
和 getLength
副本,并且每个实例都将引用相同的函数。这对性能会更好,并且需要更少的内存。
这就是原型的用武之地。原型是继承但不被实例复制的属性的“蓝图”。所以这意味着对于一个对象的所有实例,它在内存中只存在一次,并且由所有这些实例共享。
现在,再次考虑 keyValueStore
对象。我可以这样重写它:
var keyValueStore = (function() {
var count = 0;
var kvs = function() {
count++;
this.data = {};
};
kvs.prototype = {
'get' : function(key) { return this.data[key]; },
'set' : function(key, value) { this.data[key] = value; },
'delete' : function(key) { delete this.data[key]; },
'getLength' : function() {
var l = 0;
for (p in this.data) l++;
return l;
}
};
return {
'create' : function() { return new kvs(); },
'count' : function() { return count; }
};
})();
这与以前版本的 keyValueStore
对象完全相同,只是它的所有方法现在都放在原型中。这意味着,所有 100 个实例现在都共享这四种方法,而不是每个都有自己的副本。
概括:
函数是 javascript 中的对象,因此可以具有属性
(构造函数)函数始终具有原型属性
当使用 new 关键字将函数用作构造函数时,对象将获得原型。可以在新创建对象的 __proto__ 属性上找到对此原型的引用。
这个 __proto__ 属性是指构造函数的原型属性。
例子:
函数人(姓名){ this.name = name; } let me = new Person('willem'); console.log(Person.prototype) // Person 有一个原型属性 console.log(Person.prototype === me.__proto__) // 实例的 __proto__ 属性是指函数的原型属性。
为什么这很有用:
Javascript 在查找对象的属性时有一种称为“原型继承”的机制,这基本上是这样做的:
首先检查属性是否位于对象本身上。如果是这样,则返回此属性。
如果该属性不在对象本身上,它将“爬上原型链”。它基本上着眼于由 proto 属性引用的对象。它在那里检查该属性是否在 proto 引用的对象上可用
如果属性不在 proto 对象上,它将沿着 proto 链一直向上爬到 Object 对象。
如果它在对象及其原型链上找不到属性,它将返回未定义。
例如:
函数人(姓名){ this.name = name; } let mySelf = new Person('Willem'); console.log(mySelf.__proto__ === Person.prototype); console.log(mySelf.__proto__.__proto__ === Object.prototype);
更新:
__proto__
属性已被弃用,尽管它已在大多数现代浏览器中实现,但获取原型对象引用的更好方法是:
Object.getPrototypeOf()
在理解这类东西时,我总是喜欢类比。在我看来,与 class bass 继承相比,“原型继承”相当令人困惑,尽管原型是更简单的范例。事实上,原型确实没有继承,所以这个名字本身就具有误导性,它更像是一种“委托”。
想象一下这个......
你在上高中,你在课堂上,有一个今天到期的测验,但你没有笔来填写你的答案。嗬!
你坐在你的朋友 Finnius 旁边,他可能有一支笔。你问,他没有成功地环顾他的办公桌,但他没有说“我没有笔”,而是和他的另一个朋友 Derp 一起检查他是否有笔。 Derp 确实有一支备用笔并将其传回给 Finnius,后者将其传递给您以完成您的测验。 Derp 将笔委托给 Finnius,Finnius 将笔委托给您使用。
这里重要的是 Derp 不会把笔给你,因为你和他没有直接的关系。
这是原型如何工作的简化示例,在其中搜索数据树以查找您要查找的内容。
https://i.stack.imgur.com/uy5ce.png
只是您已经有一个带有 Object.new
的对象,但在使用构造函数语法时仍然没有对象。
重要的是要了解对象的原型(可通过 Object.getPrototypeOf(obj) 或已弃用的 __proto__ 属性获得)和构造函数的原型属性之间的区别。前者是每个实例的属性,后者是构造函数的属性。即 Object.getPrototypeOf(new Foobar()) 与 Foobar.prototype 指的是同一个对象。
参考:https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes
原型通过克隆现有对象来创建新对象。所以当我们考虑原型时,我们真的可以考虑克隆或复制某些东西,而不是虚构它。
如果您想从基础知识中了解原型和基于原型的继承的概念,请查看官方 MDN 文档,他们解释得很好。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
在继承方面,JavaScript 只有一种构造:对象。每个对象都有一个私有属性,该属性保存到另一个对象的链接,称为其原型。该原型对象有自己的原型,依此类推,直到到达一个对象,其原型为 null。根据定义,null 没有原型,是这个原型链中的最后一环。
此外,这是另一个很好的资源,它使用简单的示例进行了解释 - https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes
Customer.prototype = new Person();
行,MDN 展示了一个使用Customer.prototype = Object.create(Person.prototype)
的示例,并指出 '此处的常见错误是使用 "new Person()"'。 source