JavaScript学习日记 : Day20 - call、apply、bind

1. 语法

func.call(thisArg, param1, param2, ...)//func是个函数

func.apply(thisArg, [param1,param2,...])

func.bind(thisArg, param1, param2, ...)

返回值 :

call / apply : 返回func值行结果。
bind : 返回func的拷贝,并拥有指定的this值合初始参数。

参数 :
thisArg (可选) :

  1. func的this指向thisArg对象
  2. 非严格模式下,若thisArg指定为null,undefined,则func的this指向window对象。
  3. 严格模式下,func的this为undefined
  4. 值为原始值(数字、字符串、布尔值)的this会指向该原始值的自动包装对象,如String、Number、Boolean。

param1、param2(可选):传给func的参数。

2.必须是函数才能调用call/apply/bind

call、apply、bind是挂在Function对象上的三个方法,只有函数有这些方法,例如: Object.prototype.toString就是函数,我们经常看到这样的用法Object.prototype.tostring.call(data)

3. call/apply vs bind

执行 :

  1. call/apply改变this後马上执行函数
  2. bind则是返回改变this指向後的函数,不执行该函数。

返回值 :

  1. call/apply返回func执行结果
  2. bind返回func的拷贝,并指定了func的this指向,保存了func的参数。

4. 类数组

类数组的特徵有,可以透过索引(index)调用,如array[0];具有长度属性,可以透过for循环或forEach方法。

那类数组是什麽? 顾名思义就是具备与数组特徵类似的对象。例如下面例子 :

let arrayLike = {
    0: 1,
    1: 2,
    2: 3,
    length: 3
};

类数组比较常见在譬如,获取DOM节点的方法,返回的就是一个类数组,在函数中使用augument所获取的所有参数,也是类数组。

但注意,类数组无法使用splice,push等数组原型练上的方法,那就要用到call/apply/bind的核心理念---借用方法:

以类数组做比喻,如果它想借用Array原型链上slice的方法,可以这样:

let domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

借用方法的场景

  1. 类数组对象借用数组的方法
// 类数组对象
let arrayLike = {
  0: "name",
  1: "height",
  length: 2
};

Array.prototype.push.call(arrayLike, "weight", "id");

console.log(arrayLike);
// {"0":"name","1":"height","2":"weight","3":"id","length":4}
function hash() {
  alert( [].join.call(arguments) ); // 1,2
}

hash(1, 2);
  1. apply获取数组最大最小值

apply直接使用数组作为参数,也省去了展开数组这一步。

const arr = [15, 6, 12, 13, 16];

const max = Math.max.apply(Math, arr); // 16

const min = Math.min.apply(Math, arr); // 6

5. call、apply使用时机

  1. 参数数量/顺序确定call,参数数量/顺序不确定的话就用apply。
  2. 参数数量少用call,多则用apply。
  3. 参数集合已经是数组的情况,用apply,例如上面的获取最大/最小值。

参数数量不确定的例子 :

const obj = {
    age: 24,
    name: 'John',
}

const obj2 = {
    age: 25
}

// 依据某些条件来决定要传入的参数数量与顺序
function callObj(thisAge, fn) {
    let params = [];

    if (thisAge.name) {
        params.push(thisAge.name);
    }

    if (thisAge.age) {
        params.push(thisAge.age);
    }

    fn.apply(thisAge, params); // 参数数量不确定,用apply
}

function handle(...params) {
    console.log('params', params);
}

callObj(obj, handle); // params  ["John", 24]
callObj(obj2, handle); // params [25]

6. bind使用的时机

  1. 保存函数参数

经典面试题 :

for(var i = 1; i < 5; i++) {
    setTimeout(function test() {
        console.log(i) // 5 5 5 5 5
    },i*1000)
}

原因在於等到非同步setTimeout执行时,i已经变成5了。用bind可以解决此问题 :

for(var i = 1; i < 5; i++) {
    setTimeout(function test(i) {
        console.log(i) // 5 5 5 5 5
    }.bind(null, i),i*1000)
}

实际上这里也运用了闭包。它保存了函数this的指向(setTimeout与setInterval预设都是指向window,这边只是为了只用bind方法,null也是指向window)、初始参数,每次i边更都会被bind的闭包存起来,所以输出1~5。

  1. 回调函数丢失问题
class Cat {
    constructor(name,callback) {
        this.name = name;
        this.callback = callback;
        this.callback();
    }
}

class Dog {
    constructor(name) {
        this.name = name;
        this.friend = new Cat('white cat',this.run)
    }
    
    run() {
        console.log(`${this.name} run with cat!`);
    }
}

new Dog("white dog");

我们预期希望是显示出white dog run with cat!,但实际上显示出white cat run with cat!,原因在於当new Cat时,所传入的this.run是记忆体位子(call by reference),并没有邦定this的指向,这时候可以用bind来解决:

class Cat {
    constructor(name,callback) {
        this.name = name;
        this.callback = callback;
        this.callback();
    }
}

class Dog {
    constructor(name) {
        this.name = name;
        this.friend = new Cat('white cat',this.run.bind(this))
    }
    
    run() {
        console.log(`${this.name} run with cat!`);
    }
}

new Dog("white dog"); // white dog run with cat!

或是把run改为arrow function也可以:

class Cat {
    constructor(name,callback) {
        this.name = name;
        this.callback = callback;
        this.callback();
    }
}

class Dog {
    constructor(name) {
        this.name = name;
        this.friend = new Cat('white cat',this.run.bind(this))
    }
    
    run = () => {
        console.log(`${this.name} run with cat!`);
    }
}

new Dog("white dog"); // white dog run with cat!

<<:  [寿星优惠-2] 肉肉先生 Mr.zozo #当月寿星6折

>>:  day17 不懂kotlin flow资料流? 那喝杯进口奶茶吧

day26 老板我赶时间,给我最快完成的料理 select

提醒,select仍是实验中的api,请斟酌使用 在这之前的26天,我们所用的都是我要做什麽事,就是...

[Day02] Which one is better? Oh... I mean more suitable

其实任何技术上的选型都没有最好,就像选择程序语言一样,大家都有共识 PHP 是世界上最好的...咳,...

从零开始学3D游戏设计:自定义粒子效果

这是 Roblox 从零开始系列,效果章节的第三个单元,今天你将学会如何制作自己想要的粒子效果 Pa...

Day9-滚动视差(下)_後有图样

今天继续说滚动视差 球球的部分先在scroll_thing的下方加上球球的div <div c...

DAY29 - 切版的学习历程与方向

今天就我自己的经验跟大家分享一下, 培养切版技能的方向~ 第一阶段:练习什麽静态版面都要可以切出来 ...