JavaScript 中的 new 关键字和函数调用方法详解,apply、call 和 bind 的区别
一、实现 new 关键字的思路
new 关键字的作用
当使用 new 调用构造函数时,JavaScript 会执行以下操作:
创建一个新对象
将这个新对象的原型指向构造函数的 prototype 属性
将构造函数的 this 绑定到这个新对象
执行构造函数中的代码
如果构造函数没有返回对象,则返回这个新对象
实现一个自定义的 new 方法
function myNew(constructor, ...args) {// 1. 创建一个新对象,并将其原型指向构造函数的 prototypeconst obj = Object.create(constructor.prototype);// 2. 调用构造函数,并将 this 绑定到新对象const result = constructor.apply(obj, args);// 3. 如果构造函数返回了一个对象,则返回该对象,否则返回新对象return typeof result === 'object' && result !== null ? result : obj;
}// 测试用例
function Person(name, age) {this.name = name;this.age = age;
}Person.prototype.sayHello = function() {console.log(`Hello, my name is ${this.name}`);
};const p1 = new Person('Alice', 25);
const p2 = myNew(Person, 'Bob', 30);p1.sayHello(); // Hello, my name is Alice
p2.sayHello(); // Hello, my name is Bob
二、apply、call 和 bind 的区别
这三个方法都用于改变函数执行时的 this 指向,但有一些关键区别:
1. call 方法
func.call(thisArg, arg1, arg2, ...)
立即执行函数
第一个参数是 this 的指向
后续参数是传递给函数的参数列表
示例:
function greet(greeting, punctuation) {console.log(`${greeting}, ${this.name}${punctuation}`);
}const person = { name: 'Alice' };greet.call(person, 'Hello', '!'); // Hello, Alice!
2. apply 方法
func.apply(thisArg, [argsArray])
立即执行函数
第一个参数是 this 的指向
第二个参数是一个数组或类数组对象,包含传递给函数的参数
function greet(greeting, punctuation) {console.log(`${greeting}, ${this.name}${punctuation}`);
}const person = { name: 'Bob' };greet.apply(person, ['Hi', '!!']); // Hi, Bob!!
3. bind 方法
func.bind(thisArg, arg1, arg2, ...)
不立即执行函数,而是返回一个新函数
第一个参数是 this 的指向
可以预先设置部分参数(柯里化)
返回的函数可以稍后调用
function greet(greeting, punctuation) {console.log(`${greeting}, ${this.name}${punctuation}`);
}const person = { name: 'Charlie' };
const greetCharlie = greet.bind(person, 'Hey');greetCharlie('...'); // Hey, Charlie...
三、实现 apply 或 call 方法
实现 call 方法
Function.prototype.myCall = function(context, ...args) {// 如果 context 为 null 或 undefined,则指向全局对象(浏览器中是 window)context = context || globalThis;// 创建一个唯一的属性名,避免覆盖原有属性const fnKey = Symbol('fn');// 将当前函数(this)作为 context 的一个方法context[fnKey] = this;// 调用该方法,此时 this 指向 contextconst result = context[fnKey](...args);// 删除临时添加的方法delete context[fnKey];return result;
};// 测试用例
function showInfo(age, city) {console.log(`${this.name}, ${age}, ${city}`);
}const person = { name: 'David' };showInfo.myCall(person, 28, 'New York'); // David, 28, New York
实现 apply 方法
Function.prototype.myApply = function(context, argsArray) {// 如果 context 为 null 或 undefined,则指向全局对象context = context || globalThis;// 检查 argsArray 是否是数组或类数组if (argsArray && typeof argsArray !== 'object') {throw new TypeError('CreateListFromArrayLike called on non-object');}// 创建一个唯一的属性名const fnKey = Symbol('fn');// 将当前函数作为 context 的一个方法context[fnKey] = this;// 调用该方法,传入数组参数const result = context[fnKey](...(argsArray || []));// 删除临时添加的方法delete context[fnKey];return result;
};// 测试用例
function showDetails(profession, hobby) {console.log(`${this.name} is a ${profession} who loves ${hobby}`);
}const person = { name: 'Eva' };showDetails.myApply(person, ['developer', 'hiking']);
// Eva is a developer who loves hiking
实现 bind 方法
Function.prototype.myBind = function(context, ...bindArgs) {const originalFunc = this;return function(...callArgs) {// 如果是作为构造函数调用(使用 new),则忽略绑定的 thisif (new.target) {return new originalFunc(...bindArgs, ...callArgs);}return originalFunc.apply(context, [...bindArgs, ...callArgs]);};
};// 测试用例
function introduce(greeting, punctuation) {console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}const person = { name: 'Frank' };
const boundIntroduce = introduce.myBind(person, 'Hi');boundIntroduce('!'); // Hi, I'm Frank!// 作为构造函数测试
function Person(name, age) {this.name = name;this.age = age;
}const BoundPerson = Person.myBind(null, 'DefaultName');
const p = new BoundPerson(30);
console.log(p.name, p.age); // DefaultName 30
new.target 的作用
new.target 是 JavaScript 中的一个 元属性(meta property),它用于检测函数是否是通过 new 关键字调用的:
如果函数是通过 new 调用的(如 new Foo()),new.target 会返回该函数的引用(即 Foo)。
如果函数是普通调用(如 Foo()),new.target 是 undefined。