JS中的apply、call、bind在实际开发中的应用以及实现

82 阅读2分钟
  1. 函数借用
  2. 事件处理
  3. 函数柯里化
  4. 实现继承和构造函数的调用
  5. 数组中的最大值和最小值

1.函数借用

用另外一个对象的方法来操作当前对象的数据,可以使用 call 、apply 来实现这一点

const arrayLikeObject = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3
};

const result = Array.prototype.slice.call(arrayLikeObject);
console.log(result); // 输出: ['a', 'b', 'c']

不使用call

const arrayLikeObject = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3
};

const result = [];
for (let i = 0; i < arrayLikeObject.length; i++) {
    result.push(arrayLikeObject[i]);
}

console.log(result); // 输出: ['a', 'b', 'c']

借用了数组的 slice 方法将一个类数组对象转换为数组

2.事件处理

通过使用 bind 方法,我们可以预置 this 值和部分参数,以简化事件处理。

function handleClick(event) {
    console.log('Button clicked:', this.textContent);
}
const button = document.querySelector('button');
button.addEventListener('click', handleClick.bind(button));

不使用bind

const button = document.querySelector('button');
button.addEventListener('click', (event) => {
    console.log('Button clicked:', button.textContent);
});

3.函数柯里化

使用bind

function multiply(a, b) {
    return a * b;
}

const double = multiply.bind(null, 2);
console.log(double(5)); // 输出: 10

const triple = multiply.bind(null, 3);
console.log(triple(5)); // 输出: 15

不使用bind

function multiply(a) {
    return function(b) {
        return a * b;
    };
}

const double = multiply(2);
console.log(double(5)); // 输出: 10

const triple = multiply(3);
console.log(triple(5)); // 输出: 15

4.实现继承和构造函数的调用

使用call

function Animal(name) {
    this.name = name;
}

Animal.prototype.speak = function () {
    console.log(this.name + ' makes a noise.');
};

function Dog(name, breed) {
    Animal.call(this, name); // 调用父构造函数
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
    console.log(this.name + ' barks.');
};

const dog = new Dog('Rex', 'German Shepherd');
dog.speak(); // 输出: Rex makes a noise.
dog.bark();  // 输出: Rex barks.

不使用 call

function Animal(name) {
    this.name = name;
}

Animal.prototype.speak = function() {
    console.log(this.name + ' makes a noise.');
};

function Dog(name, breed) {
    this.name = name; // 直接设置 name 属性
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
    console.log(this.name + ' barks.');
};

const dog = new Dog('Rex', 'German Shepherd');
dog.speak(); // 输出: Rex makes a noise.
dog.bark();  // 输出: Rex barks.

5.数组中的最大值和最小值

使用apply

const numbers = [5, 6, 2, 3, 7];

const max = Math.max.apply(null, numbers);
const min = Math.min.apply(null, numbers);

console.log(max); // 输出: 7
console.log(min); // 输出: 2

不使用apply

const numbers = [5, 6, 2, 3, 7];

const max = Math.max(...numbers);
const min = Math.min(...numbers);

console.log(max); // 输出: 7
console.log(min); // 输出: 2

call的实现

Function.prototype.myCall = function (context, ...args) {
    // 如果 context 为 null 或 undefined,默认设置为全局对象(在严格模式下为 undefined)
    context = context || globalThis;

    // 创建一个唯一的属性名,防止与 context 上的其他属性冲突
    const fnSymbol = Symbol('fn');

    // 将当前函数(即被调用的函数)作为 context 的一个方法
    // 这里的 this 指向调用 myCall 的函数
    context[fnSymbol] = this;

    // 使用指定的 context 和参数调用函数,并获取结果
    const result = context[fnSymbol](...args);

    // 删除临时绑定的函数
    delete context[fnSymbol];

    // 返回结果
    return result;
}
function greet(greeting, punctuation) {
    return `${greeting}, ${this.name}${punctuation}`;
}

const person = {
    name: 'Alice'
};
console.log(greet.myCall(person, 'Hello', '!')); // 'Hello, Alice!'

greet 函数临时添加为 person 对象的一个方法,使用唯一的 SymbolfnSymbol 作为属性名。这就意味着 person 对象现在有一个临时的方法 fnSymbol,这个方法指向 greet 函数。

apply的实现

Function.prototype.myApply = function(context, args) {
    // 如果 context 为 null 或 undefined,默认设置为全局对象(在严格模式下为 undefined)
    context = context || globalThis;

    // 创建一个唯一的属性名,防止与 context 上的其他属性冲突
    const fnSymbol = Symbol('fn');

    // 将当前函数(即被调用的函数)作为 context 的一个方法
    context[fnSymbol] = this;

    // 使用指定的 context 和参数数组调用函数,并获取结果
    const result = context[fnSymbol](...(args || []));

    // 删除临时绑定的函数
    delete context[fnSymbol];

    // 返回结果
    return result;
};

// 示例
function greet(greeting, punctuation) {
    return `${greeting}, ${this.name}${punctuation}`;
}

const person = {
    name: 'Alice'
};

console.log(greet.myApply(person, ['Hello', '!'])); // 输出:'Hello, Alice!'

call的实现没有什么区别

bind的实现

Function.prototype.myBind = function(context, ...args) {
    const fn = this; // 保存原函数引用

    // 返回一个新的函数
    return function(...newArgs) {
        // 合并原始绑定时传入的参数和调用新函数时传入的参数
        const combinedArgs = [...args, ...newArgs];

        // 使用指定的 context 调用原函数,并传入合并后的参数
        return fn.apply(context, combinedArgs);
    };
};

// 示例
function greet(greeting, punctuation) {
    return `${greeting}, ${this.name}${punctuation}`;
}

const person = {
    name: 'Alice'
};

const greetPerson = greet.myBind(person, 'Hello');
console.log(greetPerson('!')); // 输出:'Hello, Alice!'