这是我参与「第四届青训营 」笔记创作活动的第6天
在大项目的构建过程中,我发现有时候需要对某个浏览器内置对象的原型链上的方法作一定的修改和加强,这时候我们就需要详细的审视一下原型的的概念
以下开始正文
定义 JavaScript 类的的习惯性做法定义构造函数define a constructor
构造函数是为初始化新创建的对象而设计的函数 A constructor is a function designed for the initialization of newly created objects
构造函数的调用需要使用new 关键字
使用 new 的构造函数调用会自动创建新对象,因此构造函数本身只需要初始化该新对象的状态。onstructor invocations using new automatically create the new object, so the constructor itself only needs to initialize the state of that new object
构造函数调用的关键特征是构造函数的prototype属性被用作新对象的原型 The critical feature of constructor invocations is that the prototype property of the constructor is used as the prototype of the new object
虽然几乎所有对象都有原型,但只有少数对象具有prototype属性。
具有prototype属性的是函数对象it is function objects that have a prototype property
这意味着使用相同构造函数创建的所有对象都继承自同一对象,因此是同一类的成员 all objects created with the same constructor function inherit from the same object and are therefore members of the same class
0.在没有class关键字时使用构造函数
1.使用构造函数而不是工厂函数。
2.在不支持 ES6 class 关键字的 JavaScript 版本中创建类的惯用方法
使用工厂函数的案例
// This is a factory function that returns a new range object. 这是一个返回新range对象的工厂函数。
function range(from, to) {
// Use Object.create() to create an object that inherits from the prototype object defined below.使用 Object.create() 创建一个继承自下面定义的原型对象的对象。
// The prototype object is stored as a property of this function, and defines the shared methods (behavior) for all range objects.
// 原型对象存储为该函数的一个属性,并为所有range对象定义共享方法(行为)
// 将原型对象放在这里没有什么特别或惯用的
let r = Object.create(range.methods);
// Store the start and end points (state) of this new range object. 存储这个新range对象的起点和终点(状态)
// These are noninherited properties that are unique to this object. 这些是此对象独有的非继承属性
// 这些是非共享的、非继承的属性,它们定义每个单独的Range对象的惟一状态unshared, noninherited properties that define the unique state of each individual Range object。
r.from = from;
r.to = to;
// Finally return the new object
return r;
}
// This prototype object defines methods inherited by all range objects. 这个原型对象定义了会被所有range对象继承的方法
range.methods = {
// Return true if x is in the range, false otherwise 如果 x 在范围内,则返回 true,否则返回 false
// This method works for textual and Date ranges as well as numeric. 此方法适用于文本和日期以及数字范围
includes(x) { return this.from <= x && x <= this.to; },
// A generator function that makes instances of the class iterable. 使类的实例可迭代的生成器函数 computed name计算名字
// Note that it only works for numeric ranges.请注意,它仅适用于数字范围。
*[Symbol.iterator]() {
for(let x = Math.ceil(this.from); x <= this.to; x++)
yield x;
},
// Return a string representation of the range 返回范围的字符串表示
toString() { return "(" + this.from + "..." + this.to + ")"; }
};
//range.methods 对象使用 ES6 简写语法来定义方法,这就是为什么看不到 function 关键字的原因
// Here are example uses of a range object.
let r = range(1,3); // Create a range object r.
includes(2) // => true: 2 is in the range r.
toString() // => "(1...3)"
[...r] // => [1, 2, 3]; convert to an array via iterator
使用构造函数的案例
// This is a constructor function that initializes new Range objects. 这是一个初始化新 Range 对象的构造函数。
// Note that it does not create or return the object. It just initializes this. 它不会创建或返回对象,它只是初始化
// 后面使用new关键字就自动创建新对象了
function Range(from, to) {
// Store the start and end points (state) of this new range object.
// These are noninherited properties that are unique to this object.
this.from = from;
this.to = to;
}
// All Range objects inherit from this object. 所有Range对象都继承这个对象
// Note that the property name must be "prototype" for this to work.属性名必须是"prototype"
Range.prototype = {
// Return true if x is in the range, false otherwise
// This method works for textual and Date ranges as well as numeric.
includes: function(x) { return this.from <= x && x <= this.to; },
// A generator function that makes instances of the class iterable.
// Note that it only works for numeric ranges.
[Symbol.iterator]: function*() {
for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
},
// Return a string representation of the range
toString: function() { return "(" + this.from + "..." + this.to + ")"; }
};
// Here are example uses of this new Range class
let r = new Range(1,3); // Create a Range object; note the use of new
r.includes(2) // => true: 2 is in the range
r.toString() // => "(1...3)"
[...r] // => [1, 2, 3]; convert to an array via iterator
2种技术之间的区别
1.构造函数首字母大写
当我们将 range() 工厂函数转换为构造函数时,我们将其重命名为 Range()。
- 这是一个非常常见的编码约定
- 构造函数在某种意义上定义类 constructor functions define, in a sense, classes
- 而类的名称(按照约定)以大写字母开头。
- 常规函数和方法的名称以小写字母开头。
2.构造函数调用以新创建对象为this值
Range() 构造函数是使用 new 关键字调用的,而 range() 工厂函数是不需要关键字调用的
//工厂函数方法
let r = range(1,3); // Create a range object r.
//构造函数方法
let r = new Range(1,3); // Create a Range object; note the use of new
常规函数调用和构造函数调用有很大不同
第一个例子使用常规函数调用,第二个例子使用构造函数调用
因为 Range() 构造函数是用 new 调用的,所以它不必调用 Object.create() 或执行任何操作来创建新对象i t does not have to call Object.create() or take any action to create a new object。
新对象在调用构造函数之前自动创建,并且可以作为 this 值访问。 The new object is automatically created before the constructor is called, and it is accessible as the this value.
Range() 构造函数只需初始化this。The Range() constructor merely has to initialize this.
构造函数甚至不必返回新创建的对象。Constructors do not even have to return the newly created object.
构造函数调用自动创建一个新对象,将构造函数作为该对象的方法来调用,并返回新对象。Constructor invocation automatically creates a new object, invokes the constructor as a method of that object, and returns the new object.
这个新对象是构造函数调用的调用上下文
如果构造函数被当做常规函数来调用,那它们通常无法正常工作。
2.1使用new.target判断是否作为构造函数调用
在函数体中,您可以使用特殊表达式new.target来判断函数是否作为构造函数调用。
如果该表达式的值已定义,那么函数是作为构造函数调用的,带有new关键字
- new.target is not always a reference to the constructor it is used in: it might also refer to the constructor function of a subclass
new.target并不总是对使用它的构造函数的引用:它也可能引用子类的构造函数
表达式new.target未定义,则包含的函数是作为作为函数调用,没有new关键字。
- JavaScript的各种错误构造函数可以在没有new的情况下调用
- 如果你想在自己的构造函数中模拟这个特性,你可以这样编写它们
function C() {
if (!new.target) return new C();
// initialization code goes here
}
此技术仅适用于以这种老式方式定义的构造函数。
使用 class 关键字创建的类不允许在没有 new 的情况下调用其构造函数
3.原型对象的命名
在第一个示例中,原型是 range.methods。方便convenient、描述性descriptive ,但随意arbitrary。
在第二个例子中,原型是 Range.prototype,必须要这么命名mandatory。
Range() 构造函数的调用会自动使用 Range.prototype 作为新 Range 对象的原型。
4.方法的定义和调用相同
因为示例2演示了在 ES6 之前的 JavaScript 版本中创建类的惯用方式,所以它没有在原型对象中使用 ES6 速记method语法
两个例子中方法的实现是一样的
5.定义构造函数和方法都没有使用箭头函数
箭头的函数没有prototype属性,因此不能用作构造函数。
箭头函数从定义它们的上下文继承 this 关键字,而不是根据调用它们的对象来设置this值 arrow functions inherit the this keyword from the context in which they are defined rather than setting it based on the object through which they are invoked
这使得箭头函数对方法无用this makes them useless for methods
因为方法的定义特征是它们使用 this 来引用调用它们的实例 the defining characteristic of methods is that they use this to refer to the instance on which they were invoked.
梳理逻辑
- 新创建的对象o, 构造函数constructor,原型对象constructor.prototype
- 在构造函数调用之前,o已经被创建了,并作为构造函数调用的上下文
- 构造函数只负责初始化对象
- 所以构造函数本身作为o的方法来调用,构造函数本身就是原型对象的constructor属性(按方法的定义也叫做方法),这个constructor属性会被实例继承,所以当然也是o的方法
- constructor.prototype里面也定义了一系列方法,m1/m2/…., 这些方法会被o继承
- 所以构造函数本身成为o的方法之外,其属性值中的一些方法也会成为o的方法
- 这样,构造函数不可以是箭头函数,方法也不可以是箭头函数,因为箭头函数的this值是无法设置成o的
新的 ES6 类语法不允许使用箭头函数定义方法
1.Constructors构造函数, Class Identity类标识和instanceof
1.the identity of class:prototype object
原型对象是类标识的本质the prototype object is fundamental to the identity of a class
当且仅当两个对象继承自相同的原型对象时,它们是同一个类的实例instances of the same class。
构造函数不是本质的The constructor function that initializes the state of a new object is not fundamental
两个构造函数可能具有指向同一个原型对象的prototype属性,这样两个构造函数都可以用来创建同一个类的实例。
2.the public identity of class:constructor
尽管构造函数不像原型prototypes那样本质,但构造函数充当了类的公共面孔constructor serves as the public face of a class。
最明显的是,构造函数的名称通常被用作类的名称。
例如,我们说Range()构造函数创建Range对象the Range() constructor creates Range objects
在测试对象在类中的成员关系时,构造函数被用作instanceof操作符的右操作数。
2.1 instanceof
如果我们有一个对象r,并且想知道它是否是一个Range对象,我们可以这样写:
r instanceof Range // => true: r inherits from Range.prototype
如果 o 继承自 C.prototype,则表达式 o instanceof C 的计算结果为真。
继承不必是直接的:如果 o 继承自从 C.prototype 继承的对象的对象,则表达式仍将计算为真。if o inherits from an object that inherits from an object that inherits from C.prototype, the expression will still evaluate to true.
从技术上讲,instanceof 运算符并没有检查 r 是否实际由 Range 构造函数初始化。
相反,它正在检查 r 是否继承自 Range.prototype。
如果我们定义一个函数 Strange() 并将它的原型设置为与 Range.prototype 相同set its prototype to be the same as Range.prototype
那么使用 new Strange() 创建的对象就 instanceof 而言将被视为 Range 对象
但它们实际上不会作为 Range 对象工作 ,因为它们的 from 和 to 属性尚未初始化
function Strange() {}
Strange.prototype = Range.prototype;
new Strange() instanceof Range // => true
尽管 instanceof 无法真正验证构造函数的使用,但它仍然使用构造函数作为其右侧,因为构造函数是类的公共标识。Even though instanceof cannot actually verify the use of a constructor, it still uses a constructor function as its righthand side because constructors are the public identity of a class.
2.2 isPrototypeOf()
如果要针对一个特定原型来测试对象的原型链,又不想使用构造函数作为中介,可以使用 isPrototypeOf() 方法。
在示例1 中,我们定义了一个没有构造函数的类,因此无法使用 instanceof。
但是,我们可以使用以下代码测试对象 r 是否是该无构造函数类that constructor-less class的成员
range.methods.isPrototypeOf(r); // range.methods is the prototype object.
2.构造函数属性The constructor Property
在第2例中,我们将Range.prototype设置为一个新的对象,其中包含了我们类的方法。
Range.prototype = {
includes: function(x) { return this.from <= x && x <= this.to; },
[Symbol.iterator]: function*() {
for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
},
toString: function() { return "(" + this.from + "..." + this.to + ")"; }
};
尽管将这些方法表达为一个单一的对象字面量的属性很方便,但实际上并没有必要创建一个新的对象。
-
任何常规的JavaScript函数(不包括箭头函数、生成器函数和异步函数)都可以被用作构造函数Any regular JavaScript function (excluding arrow functions, generator functions, and async functions) can be used as a constructor
-
而构造函数的调用需要一个
prototype属性 -
因此,每个常规的JavaScript函数都会自动有一个
prototype属性。- 除了由ES5
Function.bind()方法返回的函数。绑定的函数Bound functions没有自己的prototype属性,但如果它们作为构造函数被调用,它们会使用底层函数的原型prototype of the underlying function。 - Except functions returned by the ES5 Function.bind() method. have no prototype property of their own, but they use the prototype of the underlying function if they are invoked as constructors.
- 除了由ES5
-
这个
prototype属性的值是一个对象 -
这个对象有一个单一single的、不可列举non-enumerable的构造函数
constructor属性。- 说的是单一的属性,没有说是单一方法,因为原型对象里面还有很多可继承的方法
-
constructor属性的值是函数对象function object。
**//我自己的理解**
reg.prototype={
constructor:function object
}
//这个对象就是原型对象
//原型对象里面有一个constructor属性,其值就是一个function object
标准理解
let F = function() {}; // This is a function object. 函数对象F
let p = F.prototype; // This is the prototype object associated with F. 函数对象F有关的原型对象
let c = p.constructor; // This is the function associated with the prototype. 原型对象有关的函数
c === F // => true: **F.prototype.constructor === F** for any F **起始亦是终**
起始亦是终
对象继承自原型对象,而原型对象里有一个constructor属性,所以每个对象其实都继承了一个constructor属性,而这个属性其实就是指对象的构造函数本身
objects typically inherit a constructor property that refers to their constructor.
由于构造函数作为一个类的公共标识,这个构造函数属性给出了一个对象的类别
Since constructors serve as the public identity of a class, this constructor property gives the class of an object
//**构造函数属性指定了类**
let o = new F(); // Create an object o of class F
o.constructor === F // => true: the constructor property specifies the class
用一张图说明了构造函数、其原型对象、从原型到构造函数的反向引用,以及用构造函数创建的实例之间的关系
这张图应该从左到右看
构造函数constructor有一个prototype属性是prototype object
prototype object有一个constructor属性是constructor function
而且上述2个属性值本质上都是一个对象object
梳理逻辑
- 新创建的对象o, 构造函数constructor,原型对象constructor.prototype
- 在构造函数调用之前,o已经被创建了,并作为构造函数调用的上下文
- 构造函数只负责初始化对象
- 所以构造函数本身作为o的方法来调用,构造函数本身就是原型对象的constructor属性(按方法的定义也叫做方法),这个constructor属性会被实例继承,所以当然也是o的方法
- constructor.prototype里面也定义了一系列方法,m1/m2/…., 这些方法会被o继承
- 所以构造函数本身成为o的方法之外,其属性值中的一些方法也会成为o的方法
- 这样,构造函数不可以是箭头函数,方法也不可以是箭头函数,因为箭头函数的this值是无法设置成o的
在第二个例子中,Range 类用它自己的对象覆盖了预定义的 Range.prototype 对象overwrites the predefined Range.prototype object with an object of its own
它定义的新原型对象prototype object没有constructor属性
所以Range类的实例没有constructor属性
Range.prototype = {
**//缺一个constructor属性**
includes: function(x) { return this.from <= x && x <= this.to; },
[Symbol.iterator]: function*() {
for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
},
toString: function() { return "(" + this.from + "..." + this.to + ")"; }
};
我们可以通过向原型对象明确地添加一个构造函数adding a constructor to the prototype来补救这个问题。
Range.prototype = {
constructor: Range, // Explicitly set the constructor back-reference /* method definitions go here */ };
另一种在旧的JavaScript代码中常见的技术,就是使用预定义的原型对象predefined prototype object及其constructor属性,然后用代码一个一个地给对象添加方法
// Extend the predefined Range.prototype object
// so we don't overwrite the automatically created Range.prototype.constructor property.
// 这样我们就不会覆盖自动创建的Range.prototype.constructor属性了
Range.prototype.includes = function(x) {
return this.from <= x && x <= this.to;
};
Range.prototype.toString = function() {
return "(" + this.from + "..." + this.to + ")";
};