本文深入解析JavaScript中构造函数与普通函数的区别,详细阐述new运算符在对象创建时的内部工作流程,包括原型链的建立、this绑定及返回值处理。通过代码示例分析cat与Animal的关系,解释属性查找规则及instanceof判断机制,揭示new在实现JS继承中的核心意义。
JavaScript构造函数与new运算符概述
在JavaScript中,函数具有双重身份:它既可作为普通函数直接调用,也可作为构造函数使用。当使用new关键字来实例化对象时,该函数即被视为构造函数;若直接调用,则视为普通函数。
普通函数的创建方式主要包括三种:显式声明、匿名定义以及使用new Function()构造函数。
当使用new关键字创建新对象时,JavaScript引擎会将新对象的原型([[Prototype]])指向构造函数的prototype属性。这一操作在新对象与函数对象之间建立了原型链,使得新对象能够访问构造函数原型对象中定义的方法和属性。
与其他高级语言类似,JavaScript也提供了构造函数和new运算符,用于实例化类并在内存中分配对象。然而,鉴于JavaScript中“万物皆对象”的特性,为何仍需通过new来生成对象?本文将深入探讨JavaScript中new运算符的底层奥秘。
new运算符的代码解读
1、认识new运算符
function Animal(name){
this.name = name;
}
Animal.color = "black";
Animal.prototype.say = function(){
console.log("I'm " + this.name);
};
var cat = new Animal("cat");
console.log(
cat.name, //cat
cat.color //undefined
);
cat.say(); //I'm cat
console.log(
Animal.name, //Animal
Animal.color //back
);
Animal.say(); //Animal.say is not a function
2、代码解读
第1至3行定义了一个名为Animal的函数,并在其this上下文中定义了name属性,该属性的值取决于函数执行时传入的参数。
第4行在Animal函数对象本身(作为静态成员)上定义了一个名为color的属性,并赋值为“black”。
第5至7行在Animal函数的原型对象prototype上定义了一个say()方法,该方法用于输出this.name的值。
第8行使用new关键字创建了一个名为cat的新对象。
第10至14行中,cat对象尝试访问name和color属性,并调用say方法。
第16至20行中,Animal对象尝试访问name和color属性,并调用say方法。
new运算符的内部工作流程解析
3、重点解析
第8行代码是整个示例的关键:
var cat = new Animal("cat");
Animal本身是一个普通函数,但当通过new关键字创建对象时,它充当构造函数的角色。
JavaScript引擎在执行该行代码时,内部执行了一系列操作。我们可以用伪代码模拟其工作流程如下:
new Animal("cat") = {
var obj = {};
obj.__proto__ = Animal.prototype;
var result = Animal.call(obj,"cat");
return typeof result === 'object'? result : obj;
}
(1)创建一个空对象obj;
(2)将obj的[[Prototype]]指向构造函数Animal的prototype属性,从而建立obj对象的原型链:obj -> Animal.prototype -> Object.prototype -> null。
(3)在obj的执行环境中调用Animal函数,并传递参数“cat”。这相当于执行var result = obj.Animal(“cat”)。
(4)检查第3步的返回值。如果返回值为null或未返回非对象类型的值,则返回obj作为新对象;否则,将返回值作为新对象返回。
理解其运行机制后,我们知道cat实际上是过程(4)的返回值。因此,我们对cat对象的认知更加清晰:
cat的原型链结构为:cat->Animal.prototype->Object.prototype->null
cat对象上新增了一个属性:name。
分析完cat的生成过程,我们进一步观察输出结果:
cat.name -> 在过程(3)中,obj对象产生了name属性。因此,cat.name即对应这里的obj.name。
cat.color -> cat首先查找自身是否有color属性,若未找到则沿原型链向上查找。在上述示例中,color仅在Animal对象上定义为静态属性,并未定义在其原型链上,因此无法找到。
cat.say -> cat首先查找自身是否有say方法,若未找到则沿原型链向上查找。在上述示例中,我们在Animal的prototype上定义了say方法,因此成功在原型链上找到该方法。
此外,在say方法中访问this.name时,这里的this指向调用者obj,因此输出的是obj.name的值。
对于Animal对象而言,它本身也是一个对象,因此在访问属性和方法时同样遵循上述查找规则。所以:
Animal.color -> “black”
Animal.name -> “Animal”。Animal首先查找自身的name属性,找到了name,但这个name并非我们自定义的name,而是函数对象内置的属性。
通常情况下,函数对象在创建时会内置name属性,并将函数名赋值给该属性(仅适用于函数对象)。
Animal.say -> Animal在自身未找到say方法,会沿其原型链继续查找。那么,Animal的原型链结构是什么?
从测试结果来看:Animal的原型链结构如下:
Animal -> Function.prototype -> Object.prototype -> null
因此,Animal的原型链上并未定义say方法!
new运算符在继承中的意义
4、new存在的意义
在认识new运算符之后,我们回到开篇提出的问题:既然JavaScript中万物皆对象,为何还要通过new来生成对象?
要解答这个问题,我们首先需要厘清cat和Animal之间的关系:
通过上述分析,我们发现cat继承了Animal中的部分属性,因此可以简单理解:Animal和cat之间存在继承关系。
另一方面,cat是通过new生成的对象,那么cat是否真的是Animal的实例对象?我们先来了解JavaScript如何定义“实例对象”。
A instanceof B 如果上述表达式返回true,JavaScript认为A是B的实例对象。我们用此方法来判断cat和Animal的关系。
cat instanceof Animal; //true 从执行结果看,cat确实是Animal的实例。为了证实这一结果,我们再来了解JavaScript中instanceof的判断规则:
var L = A.__proto__;
var R = B.prototype;
if(L === R)
return true;
如果A的[[Prototype]]等价于B的prototype,则返回true。
在new的执行过程(2)中,cat的[[Prototype]]指向了Animal的prototype,因此cat和Animal符合instanceof的判断条件。
因此,我们可以认为:cat是Animal的实例对象。
总结
5、总结
在JavaScript中,通过new可以生成原对象的一个实例对象,该实例对象继承了原对象的属性和方法。因此,new存在的意义在于它实现了JavaScript中的继承机制,而不仅仅是实例化了一个对象!
以上就是本文的全部内容,希望本文的内容对大家的学习或工作能带来一定的帮助。如果有疑问,欢迎留言交流,感谢大家对本站的支持!