本文深入解析 JavaScript 中构造函数与普通函数的区别,重点阐述 new 关键字执行时的四个步骤(创建对象、绑定 this、关联原型、执行代码)。同时详细说明 call/apply 的作用以及构造函数中 return 语句对返回值的不同影响,帮助开发者准确理解对象创建机制。
问题概述
问题
什么是构造函数? 构造函数与普通函数区别是什么? 用new关键字的时候到底做了什么? 构造函数有返回值怎么办? 构造函数能当普通函数调用吗?
以下是我的一些理解,理解错误的地方恳请大家帮忙指正,谢谢!
this 指向机制
关于 this 关键字 this 始终指向当前正在执行函数或方法的拥有者(owner)。例如:
function test(){
console.log(this);
}
test();
//Window {top: Window, window: Window, location: Location, external: Object, chrome: Object…}
在上述代码中,我们在页面中定义了一个 test() 函数并直接调用。当函数定义在全局作用域时,其拥有者即为当前页面,也就是 window 对象。
this 指向的几种常见情况
1. 全局作用域中调用
this.name // this 指向 window 对象 2. 普通函数调用
test();// test() 函数内部的 this 同样指向 window 对象 3. 对象方法调用
obj1.fn();// obj1 对象的 fn() 方法中,this 指向 obj1 4. 调用构造函数 var dog=new Dog();// 构造函数内部的 this 指向新创建的实例对象,即这里的 dog
call 和 apply 的作用相同,仅参数传递方式不同:call 接受多个独立参数,apply 接受参数数组。 简单来说,call 和 apply 用于当一个对象缺少某个函数或方法时,调用另一个对象的现成函数/方法。其核心机制是通过替换 this 为当前对象实例,从而改变函数运行时的上下文环境。 例如:
function Dog(){
this.sound="汪汪汪";
}
Dog.prototype.bark=function(){
alert(this.sound);
}
现在我有另一个 cat 对象:
var cat={sound:'喵喵喵'}
我希望让 cat 对象也能调用 bark 方法,此时无需重新定义,可直接通过 call/apply 调用 Dog 类的 bark 方法:
Dog.prototype.bark.call(cat);
或者:
dog.bark.call(cat);
增加参数传递的示例:
function Dog(){
this.sound="汪汪汪";
}
Dog.prototype.bark=function(words){
alert(this.sound+" "+words);
}
var dog=new Dog();
dog.bark("有小偷");//alert:汪汪汪 有小偷
Dog.prototype.bark.call(cat,"饿了");//alert:喵喵喵 饿了
普通函数与构造函数的区别
普通函数 这是一个简单的普通函数:
function fn(){
alert("hello sheila");
}
fn();//alert:hello sheila
与普通函数相比,构造函数具有以下四个显著特点:
1. 无需使用 new 关键字调用
fn();2. 可以使用 return 语句返回值
function fn(a,b){
return a+b;
}
alert(fn(2,3));//alert:5
3. 函数内部不建议使用 this 关键字 我们之所以说不建议使用,是因为若强行使用需注意其后果。如果在普通函数内部使用 this 定义变量或函数,由于此时 this 指向全局 window 对象,这将无意中为 window 添加全局变量或函数。
function greeting(){
this.name="sheila";
alert("hello "+this.name);
}
greeting();//alert:hello sheila
alert(window.name);//alert:sheila
4. 函数命名采用驼峰式,且首字母小写
构造函数 在 JavaScript 中,使用 new 关键字来调用定义的构造函数。默认情况下,它返回一个新对象,该对象拥有构造函数中定义的变量和函数/方法。
举个栗子:
function Prince(name,age){
this.gender="male";
this.kind=true;
this.rich=true;
this.name=name;
this.age=age;
}
Prince.prototype.toFrog=function(){
console.log("Prince "+this.name+" turned into a frog.");
}
var prince=new Prince("charming",25);
prince.toFrog();//Prince charming turned into a frog.
prince.kind;//true
与普通函数相比,构造函数具有以下显著特点:
1. 必须使用 new 关键字调用
var prince=new Prince("charming",25);
2. 函数内部可以使用 this 关键字 在构造函数内部,this 指向正在构建的新对象。使用 this 定义的变量或函数/方法即为实例变量或实例函数/方法。这些成员必须通过实例才能访问,无法通过类型名直接访问。
prince.age;//25 Prince.age;//undefined
3. 默认不使用 return 返回值 构造函数无需显式使用 return 返回特定值,默认会返回 this,即新创建的实例对象。当然,也可以使用 return 语句,但返回值的具体表现取决于 return 值的类型,细节将在下文介绍。
4. 函数命名建议首字母大写,以便与普通函数区分。 这并非严格的命名规范,但作为一种良好的编码习惯建议如此书写。
new 关键字执行原理与返回值处理
使用 new 关键字实例化时发生了什么? 以上文中的 Prince() 函数为例:
1. 第一步,创建一个空对象。
var prince={}
2. 第二步,将构造函数 Prince() 中的 this 指向新创建的对象 prince。 3. 第三步,将 prince 的 __proto__ 属性指向 Prince 函数的 prototype,从而建立对象与原型之间的关系。 4. 第四步,执行构造函数 Prince() 内部的代码。
构造函数有 return 值怎么办? 当构造函数内部没有显式调用 return 时,默认返回 this 对象,即新创建的实例对象。 当构造函数内部调用 return 时,分为两种情况:
1. return 的是五种简单数据类型:String,Number,Boolean,Null,Undefined。 在这种情况下,忽略 return 的值,依然返回 this 对象。
2. return 的是 Object 在这种情况下,不再返回 this 对象,而是返回 return 语句指定的返回值。
function Person(name){
this.name=name;
return {name:"cherry"}
}
var person=new Person("sheila");
person.name;//cherry
p;//Object {name: "cherry"}