new Function()
new
JavaScript中有一个关键字,在函数调用之前使用该关键字将创建一个新对象并执行该函数中的所有代码。this函数主体中的任何引用都将指向新对象。最后,如果未明确返回其他对象,则将返回此新对象。我们来看一些代码。
function Animal() {
this.type = "mammal";
this.breathe = function() {
console.log("I'm breathing");
};
}
var animal = new Animal();
console.log(animal.type); // "mammal"
animal.breathe(); // "I'm breathing"
看到我们的Animal函数用于构造对象,我们将其称为构造函数。再次注意的大写Animal。就像OLOO方法中的对象名一样,这仅是约定,并且是将构造函数与普通函数区分开的。
当在上执行new Animal()时line 9,将在构造函数中创建一个新对象。Lines 2 & 4在此新对象上设置属性,最后隐式返回该对象。此返回的对象分配给animalon line 9。让我们看一下使用这种方法的好处之一。为了解决使用工厂函数的第一个缺点,我们现在可以访问一个名为的属性,该属性.constructor可以告诉我们创建对象的函数的名称。
// function Animal() {
// this.type = "mammal";
// this.breathe = function() {
// console.log("I'm breathing");
// };
// }
// var animal = new Animal();
console.log(animal.constructor === Animal); // true
console.log(animal.constructor) // f Animal() {
// this.type = "mammal";
//
// this.breathe = function() {
// console.log("I'm breathing");
// }
// }
有趣。那是一个不错的属性,但它到底是哪里来的呢?让我们看一下直接在animal对象上定义的属性。我们可以使用方法来做到这一点Object.getOwnPropertyNames(obj)
。
// function Animal() {
// this.type = "mammal";
// this.breathe = function() {
// console.log("I'm breathing");
// };
// }
// var animal = new Animal();
console.log(Object.getOwnPropertyNames(animal)); // ["type", "breathe"]
嗯,所以如果constructor不归animal它所有,那么必须在animal`s原型链上定义它。那么animal.proto指向哪里呢?
// function Animal() {
// this.type = "mammal";
// this.breathe = function() {
// console.log("I'm breathing");
// };
// }
// var animal = new Animal();
console.log(animal.__proto__); // {constructor: ƒ}
好的,所以animal.proto直接指向包含constructor 属性的对象。所以现在的问题是,该对象位于何处,它叫什么?不幸的是,指向该对象的属性的名称恰好是一个非常命名的名称,可能会使新开发人员感到困惑。此属性在我们的Animal函数中定义。在JavaScript中,函数本身就是具有各自属性的对象。这些属性之一是prototype,它的值是先前找到的包含该constructor属性的对象。因此,对于上面的示例,构造函数为Animal。Animal有一个prototype属性。此prototype属性的值是包含该constructor属性的对象。
// function Animal() {
// this.type = "mammal";
// this.breathe = function() {
// console.log("I'm breathing");
// };
// }
console.log(Animal.prototype); // {constructor: ƒ}
看起来熟悉?确实Animal.prototype是所指向的对象animal.proto。非常重要的是,当使用关键字new从构造函数创建对象时,会发生此链接。
// function Animal() {
// this.type = "mammal";
// this.breathe = function() {
// console.log("I'm breathing");
// };
// }
// var animal = new Animal();
console.log(animal.__proto__ === Animal.prototype); // true
因此,我们需要建立的最后一件事是constructor此对象中属性的值{constructor: f}。我们可以看到它是一个函数,但是它是什么功能?
// function Animal() {
// this.type = "mammal";
// this.breathe = function() {
// console.log("I'm breathing");
// };
// }
console.log(Animal.prototype.constructor); // ƒ Animal() {
// this.type = "mammal";
// this.breathe = function() {
// console.log("I'm breathing");
// };
// }
嗯,是的,它与我们在example10.js调用时在上面看到的功能相同animal.constructor。因此,constructor这里的属性只是指向我们原始的构造函数Animal。这样做的一个很大的优势就是它使我们可以查询对象以找出构造它们的函数的名称。
哎呀,要花很多钱,但要承认,如果没有视觉帮助,可能很难遵循。让我们纠正这一点并更新我们的心理模型。下面是我们将开始使用的代码片段。
尝试绘制伪古典模式的心理模型时,事情很快就会变得很疯狂,因此我们将慢慢来,一次建立一个模型。首先,我们知道每个函数本身都是一个对象,并具有一个名为的属性prototype。

我们也知道此prototype属性的值是一个包含constructor属性的对象。

我们发现该constructor属性指向构造函数,因此我们也添加此细节。

现在,我们将使用Animal构造函数创建一个新mammal对象。
function Animal() {}
var mammal = new Animal();
我们知道,的proto属性mammal现在指向引用的同一对象Animal.prototype。让我们来看看它…

现在,我们可以使用这一事实在所引用的对象上定义方法Animal.prototype。我们这样做是为了让所创建的每个对象共享这些方法Animal。由创建的所有对象Animal的proto属性都将设置为引用该对象,从而解决了使用前面讨论的工厂函数的第二个缺点。
让我们通过在中添加一个breathe属性来测试这种共享方法的概念Animal.prototype。
function Animal() {}
var mammal = new Animal();
Animal.prototype.breathe = function() {
console.log("I'm breathing");
}
mammal.breathe(); // "I'm breathing"
更新我们的思维模式…

现在,我们可以将代码添加到Animal构造函数中,该构造函数将type在mammal对象上创建属性。
function Animal() {
this.type = "mammal";
}
var mammal = new Animal();
Animal.prototype.breathe = function() {
console.log("I'm breathing");
}
mammal.breathe(); // "I'm breathing"
console.log(mammal.type); //"mammal"
现在,每次创建新对象Animal时,函数体内的代码就会执行,新对象将代替this。结果将是在type: "mammal"新mammal对象上定义的属性。
注意:我们可以定义构造函数以接受任意数量的参数。这将允许我们传递要分配给的不同值this.type。我们暂时将事情简化,并将每个新对象的类型设置为"mammal"。

接下来需要看的是如何在伪经典模式中模拟继承的概念。我们可以扩大我们的代码,包括构造函数Dog和Terrier。
// function Animal() {
// this.type = "mammal";
// }
// Animal.prototype.breathe = function() {
// console.log("I'm breathing");
// }
function Dog() {}
function Terrier() {}
var mammal = new Animal();

现在最紧迫的问题是……我们如何将这些功能链接在一起?我们希望能够dog从中Dog继承对象Animal。同样,我们希望能够terrier从中Terrier继承对象Dog。
有两种方法可以建立这种分层方法。第一种方法使我们可以继承从父构造函数创建的新对象将有权访问的所有属性和方法(执行函数的主体)。第二个让我们仅继承在父构造函数的prototype对象上定义的属性(不会继承在函数主体中定义的属性)。
让我们开始做这两个。首先,我们将Dog继承breathe()自,Animal同时还对其type定义了属性。然后,我们将研究在Terrier和之间建立关系Dog。这有很多,所以我们一次要走一步。
将原型设置为指向新功能
我们希望Dog构造函数可以访问通过Animal构造函数创建的对象可以使用的所有属性。我们知道,通过构造函数创建的对象将有权访问构造函数prototype对象中定义的所有属性。我们可以在上图中看到,mammal.proto它指向Animal.prototype。因此,让我们尝试Dog.prototype指向一个对象,该对象将使我们能够访问所需的原型链。
我们可以看到line 12,我们使用的new Animal()创建一个新的对象,必须获得我们想要的属性Dog来访问。让我们再次做同样的事情,但是这次我们将Dog.prototype指向这个新对象。看看下面的代码,再加上我们更新的思维模型。
// function Animal() {
// this.type = "mammal";
// }
// Animal.prototype.breathe = function() {
// console.log("I'm breathing");
// }
// function Dog() {}
// function Terrier() {}
// var mammal = new Animal();
Dog.prototype = new Animal();

请注意,我们如何重定向Dog.prototype到新对象。.constructor现在不再引用包含该属性的旧对象,因此将对其进行垃圾回收。

所以现在如果我们创建一个dog对象,它的原型链应该链接到这个新对象。

dog现在应该可以访问type和breathe 属性。
// function Animal() {
// this.type = "mammal";
// }
// Animal.prototype.breathe = function() {
// console.log("I'm breathing");
// }
// function Dog() {}
// function Terrier() {}
// var mammal = new Animal();
// Dog.prototype = new Animal();
var dog = new Dog();
console.log(dog.type); // "mammal"
dog.breathe(); // "I'm breathing"
现在,我们还需要解决另外一个松散的问题。你看到了什么吗?让我们尝试找出创建了什么构造函数dog。
console.log(dog.constructor); // ƒ Animal() {
// this.type = "mammal";
// }
嗯,那不是很对吗?我们希望我们dog.constructor指出的Dog不是Animal。还记得我们被重新分配Dog.prototype指向一个new Animal对象吗?结果是没有任何东西引用包含该constructor属性的旧对象,因此被垃圾回收了。因此,现在我们必须手动将constructor属性添加到Dog.prototype。
Dog.prototype.constructor = Dog;
console.log(dog.constructor); // ƒ Dog() {}

们可以轻松地识别出用于创建什么构造函数dog。现在我们已经将Dog构造函数设置为要继承Animal,让我们建立Terrier和之间的关系Dog。记住,我们只希望从创建的对象Terrier继承其中定义的行为或方法,Dog.prototype因此我们将使用另一种技术来设置这种关系。
将原型设置为指向Object.create(obj)
Object.create(obj)通过阅读本文开头附近的OLOO模式方式,我们了解了所有信息。它将创建一个新对象并设置该新对象的proto属性,使其指向作为参数传入的对象。为了帮助说明这种建立层次关系的方法与最后一种方法之间的区别,我们将在Dog构造函数的主体中添加一行代码this.legs = 4。这是为了说明此属性不会以继承的Terrier方式type继承Dog。
不可否认,我们的思维模式变得非常混乱,因此我们将省略dog和mammal对象。看看下面的代码,特别是最后一行,我们将其设置Terrier.prototype为指向从创建的新对象Dog.prototype。
// function Animal() {
// this.type = "mammal";
// }
// Animal.prototype.breathe = function() {
// console.log("I'm breathing");
// }
function Dog() {
this.legs = 4;
}
function Terrier() {}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Terrier.prototype = Object.create(Dog.prototype);

现在,像以前一样,我们已重定向Terrier.prototype到一个新对象。因此,它失去了对.constructor财产的任何参考。因此,我们需要手动将其添加回去。
Terrier.prototype.constructor = Terrier;

在上面和后面的原型链中检查心理模型,我们可以看到所有属性Terrier.prototype
都可以访问。它有一个constructor
属性。它还具有__proto__
指向的属性Dog.prototype
,该type
属性具有属性。最后,Dog.prototype.__proto__
指出Animal.prototype
,在其breathe
已经被定义。
请注意,此原型链无法访问legs
属性。如果我们从中创建一个新对象,Terrier
则可以访问上述原型链,而该原型链也不能访问legs
。让我们rex
从创建一个新对象,Terrier
然后进行测试。

让我们看看哪些属性rex可以访问。
// function Animal() {
// this.type = "mammal";
// }
// Animal.prototype.breathe = function() {
// console.log("I'm breathing");
// }
// function Dog() {
// this.legs = 4;
// }
// function Terrier() {}
// Dog.prototype = new Animal();
// Dog.prototype.constructor = Dog;
// Terrier.prototype = Object.create(Dog.prototype);
Terrier.prototype.constructor = Terrier;
var rex = new Terrier();
console.log(rex.constructor); // f Terrier() {}
console.log(rex.type); // "mammal"
rex.breathe(); // "I'm breathing"
console.log(rex.legs); // undefined
console.log(Object.getOwnPropertyNames(rex)) // []
这说明了将构造函数设置为prototype等于使用创建的对象与new将其设置为等于使用创建的对象之间的主要区别Object.create(obj)。随着new我们造成的构造函数中的代码运行,并创建一个带有原型链,但有一个链接Object.create(obj),我们只是简单地创建链接,而不在构造函数中执行代码。
该constructor属性的另一个有用好处是我们可以使用它来创建新对象。我们知道它指向构造函数,所以我们有一个对象,rex在这种情况下,我们不知道Terrier。我们要创建另一个类似于的对象rex。我们可以访问rex.constructor并调用new它。这相当于调用new Terrier();
var spot = new rex.constructor;
console.log(spot.constructor); // f Terrier() {}
console.log(spot.type); // "mammal"
spot.breathe(); // "I'm breathing"
console.log(spot.legs); // undefined
console.log(Object.getOwnPropertyNames(spot)); // []
它在哪里结束?
我们已经介绍了两种最常见的JavaScript设计模式的细节。每天随意称呼,让您的大脑去吸收所有这些信息。但是,如果您想进一步深入原型链并找出它的结尾,请继续阅读。
如果我们遵循心智模型中的原型链,我们可以看到它最终在处Animal.prototype。为了清楚起见,我们将集中精力于心智模型中包含Animal构造函数的部分。我们知道原型链到此就完成了,因此不需要在屏幕上堆满多余的信息。

我们知道Animal.prototype是一个对象。我们可以看到它包含属性constructor和breathe。但是,正如我们之前所说,所有对象都有一个proto属性。那么,该对象的proto属性是什么,它的作用是什么?
// function Animal() {
// this.type = "mammal";
// }
// Animal.prototype.breathe = function() {
// console.log("I'm breathing");
// }
// var mammal = new Animal();
console.log(Animal.prototype.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

嗯,这个对象是什么还不是很明显。我们可以看到这个未知对象constructor虽然具有属性。让我们看看导致我们前进的地方。
console.log(Animal.prototype.__proto__.constructor); // f Object() { [native code] }

那个未知的对象开始看起来很像Object构造函数的原型对象。让我们来看看…
console.log(Animal.prototype.__proto__ === Object.prototype); // true

ObjectJavaScript使用此构造函数创建对象。使用关键字调用此构造函数new将返回一个空对象{}。
好的,我们快要到达终点了,让我们继续学习。我们以前确定为的先前未知的对象Object.prototype也具有proto属性。
console.log(Object.prototype.__proto__) // null
在JavaScript中,null表示故意缺少任何对象值。因此,这很合适,可以null在我们的原型链的末尾找到。

总结
毫无疑问,本文有很多内容要吸收,特别是如果您不熟悉JavaScript。这里有一个简短的回顾,可以帮助您理解关键概念。
- JavaScript没有传统意义上的类。原型继承用于将对象链接在一起。
- JavaScript中的每个对象都有一个proto属性。如果在对象上找不到属性,它将检查此proto属性引用的对象。
- Object.create(obj)用于创建新对象并将其proto属性链接到作为参数(obj)传入的对象。
- 在对象原型链上任何地方定义的任何属性都可用于该对象
OLOO:
- 共享属性是在父对象上定义的。然后可以使用从该父对象创建其他对象Object.create(obj)。
- init()在父对象上定义的方法用于初始化具有属性的新创建的对象。此方法是可选的,但很常用。
伪古典:
- 通过使用关键字从构造函数创建新对象new。
- 调用new函数会创建一个新对象。函数中的代码在将执行上下文(this)设置为新对象的情况下执行。新创建的对象的proto属性设置为指向functionsprototype属性引用的对象。然后,新创建的对象被隐式返回。
- obj.constructor 可以用来找出创建对象的构造函数的名称。
- 可以通过更改函数.prototype属性指向的位置来模拟继承(只需记住重置.constructor属性指向的位置)。
到此结束对JavaScript设计模式的基本介绍。如果您没有完全掌握这里的所有概念,请不要担心。要了解正在发生的事情和单击所有内容,可能需要花费一些尝试和一些实践。尽管在实现这些设计模式方面还有很多要学习的知识,但是如果您能够理解此处介绍的主题,那么您将会顺利进行。
网友评论