JavaScript 中的 new 关键字和函数调用方法详解,apply、call 和 bind 的区别

JavaScript:new 关键字与函数调用方法解析

一、实现 new 关键字的思路

new 关键字的作用
当使用 new 调用构造函数时,JavaScript 会执行以下操作:

创建一个新对象

将这个新对象的原型指向构造函数的 prototype 属性

将构造函数的 this 绑定到这个新对象

执行构造函数中的代码

如果构造函数没有返回对象,则返回这个新对象

实现一个自定义的 new 方法

function myNew(constructor, ...args) {
  // 1. 创建一个新对象,并将其原型指向构造函数的 prototype
  const 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 指向 context
  const 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),则忽略绑定的 this
    if (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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值