定义

每个函数都包含两个非继承而来的方法:call()方法和apply()方法。

callapply可以用来重新定义函数的执行环境,也就是this的指向;callapply都是为了改变某个函数运行时的context,即上下文而存在的,换句话说,就是为了改变函数体内部this的指向。

每个函数都包含两个非继承而来的方法:call()方法和apply()方法。

callapply可以用来重新定义函数的执行环境,也就是this的指向;callapply都是为了改变某个函数运行时的context,即上下文而存在的,换句话说,就是为了改变函数体内部this的指向。

call()

调用一个对象的方法,用另一个对象替换当前对象,可以继承另外一个对象的属性,它的语法是:

  1. func.call(thisArg, param1, param2, ...)//func是个函数
  • thisArg:这个对象将代替Function类里this对象
  • params:一串参数列表

说明call方法可以用来代替另一个对象调用一个方法,call方法可以将一个函数的对象上下文从初始的上下文改变为obj指定的新对象,如果没有提供obj参数,那么Global对象被用于obj

apply()

call()方法一样,只是参数列表不同,语法:

  1. func.apply(thisArg, [param1,param2,...])
  • thisArg:这个对象将代替Function类里this`对象
  • argArray:这个是数组,它将作为参数传给Function

关于thisArg

  • functhis指向thisArg对象
  • 非严格模式下,若thisArg指定为null,undefined,则functhis指向window
  • 严格模式下,functhisundefined
  • 值为原始值的this会指向该原始值的自动包装对象

相同点

call()apply()方法的相同点就是这两个方法的作用是一样的。都是在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域

一般来说,this总是指向调用某个方法的对象,但是使用call()apply()方法时,就会改变this的指向,看个例子:

eg1:

  1. function add(a, b) {
  2. return a + b;
  3. }
  4. function sub(a, b) {
  5. return a - b;
  6. }
  7. console.log(add.call(sub, 2, 1));//3

eg2:

  1. function People(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. function Student(name, age, grade) {
  6. People.call(this, name, age);
  7. this.grade = grade;
  8. }
  9. var student = new Student('小明', 21, '大三');
  10. console.log(student.name + student.age + student.grade);//小明21大三

总结

callapply 的作用,完全一样,唯一的区别就是在参数上面。
call 接收的参数不固定,第一个参数是函数体内 this 的指向,第二个参数以下是依次传入的参数。
apply接收两个参数,第一个参数也是函数体内 this 的指向。第二个参数是一个集合对象(数组或者类数组)

再来一个更简单易懂的例子

eg1:

  1. var name = '小王',age = 17;
  2. var obj = {
  3. name:"小张",
  4. objAge:this.age,
  5. myFun:function(fm,t) {
  6. console.log(this.name + '年龄' + this.age)
  7. }
  8. }
  9. obj.objAge; // 17
  10. obj.myFun() // 小张年龄 undefined

eg2:

  1. var name = '小李',
  2. function show() {
  3. console.log(this.name)
  4. }
  5. show() //小李

比较一下这两者 this 的差别,第一个打印里面的 this 指向 obj,第二个全局声明的 shows() 函数 this 是 window ;

call,apply,bind都是用来重定义this的指向的,以下面的实例为例就是让 myfunthis 指向 db

  1. var name = '小王',age = 17;
  2. var obj = {
  3. name:"小张",
  4. objAge:this.age,
  5. myFun:function(fm,t) {
  6. console.log(this.name + "年龄" + this.age + "来自" + fm + "去往" + t)
  7. }
  8. }
  9. var db = {
  10. name:"德玛",
  11. age:99
  12. }
  13. obj.myFun.call(db,'成都','上海');     // 德玛 年龄 99 来自 成都去往上海
  14. obj.myFun.apply(db,['成都','上海']); // 德玛 年龄 99 来自 成都去往上海
  15. obj.myFun.bind(db,'成都','上海')(); // 德玛 年龄 99 来自 成都去往上海
  16. obj.myFun.bind(db,['成都','上海'])();   // 德玛 年龄 99 来自 成都, 上海去往 undefined

bind()

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

call/apply与bind的区别

执行:

  • call/apply改变了函数的this的指向并马上执行该函数
  • bind则是返回改变了this指向后的函数,不执行该函数

返回值:

  • call/apply 返回func的执行结果;
  • bind返回func的拷贝,并指定了functhis指向,保存了func的参数

call() apply() bind()的核心理念:借用方法

A对象有个方法,B对象因为某种原因也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?

当然是借用 A 对象的方法更便捷,既达到了目的,又节省了内存。

应用场景

判断数据类型

Object.prototype.toString

  1. function isType(data, type) {
  2. const typeObj = {
  3. "[object String]": "string",
  4. "[object Number]": "number",
  5. "[object Boolean]": "boolean",
  6. "[object Null]": "null",
  7. "[object Undefined]": "undefined",
  8. "[object Object]": "object",
  9. "[object Array]": "array",
  10. "[object Function]": "function",
  11. "[object Date]": "date", // Object.prototype.toString.call(new Date())
  12. "[object RegExp]": "regExp",
  13. "[object Map]": "map",
  14. "[object Set]": "set",
  15. "[object HTMLDivElement]": "dom", // document.querySelector('#app')
  16. "[object WeakMap]": "weakMap",
  17. "[object Window]": "window", // Object.prototype.toString.call(window)
  18. "[object Error]": "error", // new Error('1')
  19. "[object Arguments]": "arguments"
  20. };
  21. let name = Object.prototype.toString.call(data); // 借用Object.prototype.toString()获取数据类型
  22. let typeName = typeObj[name] || "未知类型"; // 匹配数据类型
  23. return typeName === type; // 判断该数据类型是否为传入的类型
  24. }
  25. console.log(
  26. isType({}, "object"), //>> true
  27. isType([], "array"), //>> true
  28. isType(new Date(), "object"), //>> false
  29. isType(new Date(), "date") //>> true
  30. );

类数组对象借用数组的方法

因为类数组不是真正的数组,所以没有数组类型上自带的一些方法,所以我们要去借用数组的方法

  1. //类数组对象
  2. var arrayLike = {
  3. 0: "OB",
  4. 1: "Koro1",
  5. length: 2
  6. };
  7. Array.prototype.push.call(arrayLike, "添加数组项1", "添加数组项2");
  8. console.log(arrayLike);
  9. //>> {"0":"OB","1":"Koro1","2":"添加数组项1","3":"添加数组项2","length":4}

apply获取最大值和最小值

apply 直接传递数组做要传递方法的参数,也省一步展开数组, 比如Math.maxMath.min来获取数组中的最大值或最小值

  1. const arr = [15, 6, 12, 13, 16];
  2. const max = Math.max.apply(Math, arr); // 16
  3. const min = Math.min.apply(Math, arr); // 6

继承

  1. // 父类
  2. function supFather(name) {
  3. this.name = name;
  4. this.colors = ['red', 'blue', 'green']; // 复杂类型
  5. }
  6. supFather.prototype.sayName = function (age) {
  7. console.log(this.name, 'age');
  8. };
  9. // 子类
  10. function sub(name, age) {
  11. // 借用父类的方法:修改它的this指向,赋值父类的构造函数里面方法、属性到子类上
  12. supFather.call(this, name);
  13. this.age = age;
  14. }
  15. // 重写子类的prototype,修正constructor指向
  16. function inheritPrototype(sonFn, fatherFn) {
  17. sonFn.prototype = Object.create(fatherFn.prototype); // 继承父类的属性以及方法
  18. sonFn.prototype.constructor = sonFn; // 修正constructor指向到继承的那个函数上
  19. }
  20. inheritPrototype(sub, supFather);
  21. sub.prototype.sayAge = function () {
  22. console.log(this.age, 'foo');
  23. };
  24. // 实例化子类,可以在实例上找到属性、方法
  25. const instance1 = new sub("OBKoro1", 24);
  26. const instance2 = new sub("小明", 18);
  27. instance1.colors.push('black')
  28. console.log(instance1);
  29. //>> {"name":"OBKoro1","colors":["red","blue","green","black"],"age":24}
  30. console.log(instance2);
  31. //>> {"name":"小明","colors":["red","blue","green"],"age":18}

bind的应用场景

保存函数参数

一道经典的面试题

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

造成这个现象的原因是等到setTimeout异步执行时,i已经变成6了。如果通过bind在内部返回一个函数,形成闭包,那么每次i的变更都会被bind保存起来

  1. for (var i = 1; i <= 5; i++) {
  2. // 缓存参数
  3. setTimeout(function (i) {
  4. console.log('bind', i) //>> 1 2 3 4 5
  5. }.bind(null, i), i * 1000);
  6. }

回调函数this丢失问题

参见

手写实现这三个方法

实现思路

  • 首先需要设置一个参数thisArg,也就是this的指向
  • thisArg封装为一个Object
  • 通过为thisArg创建一个临时方法,这样thisArg就可以调用该临时方法的对象了,会将该临时方法的this隐式的指向到thisArg
  • 执行thisArg的临时方法,并传递参数
  • 删除临时方法,返回方法的执行结果
  1. Function.prototype.myCall = function(thisArg,...arr) {
  2. //1.判断函数的合法性
  3. if(thisArg === null || thisArg === undefined) {
  4. thisArg = window
  5. } else {
  6. thisArg = Object(thisArg)
  7. }
  8. //2.搞定this的指向
  9. const specialMethod = Symbol("anything"); //创建一个不重复的常量
  10. thisArg[specialMethod] = this
  11. let result = thisArg[specialMethod](...arr)
  12. //3.删除临时方法
  13. delete thisArg[specialMethod]
  14. return result
  15. }
  1. Function.prototype.myApply = function(thisArg) {
  2. if (thisArg === null || thisArg === undefined) {
  3. thisArg = window;
  4. } else {
  5. thisArg = Object(thisArg);
  6. }
  7. const specialMethod = Symbol("anything");
  8. thisArg[specialMethod] = this;
  9. let args = arguments[1] //这里严谨一点的话还需要判断四不四类数组
  10. if (args) {
  11. result = thisArg.[specialMethod](...args)
  12. } else {
  13. result = thisArg.[specialMethod]()
  14. }
  15. delete thisArg[specialMethod]
  16. return result
  17. }

实现bind

  • 拷贝调用函数

    • 调用函数,用一个临时变量存储它
    • 只用Object.create复制调用函数的propertype给funcForBind
  • 返回拷贝的函数funcForBind
  • 调用拷贝的函数funcForBind

    • new调用判断,通过instanceof判断是否通过new调用,来决定绑定context
    • 通过call绑定this,传递参数
    • 返回调用函数的执行结果
  1. /**
  2. * 用原生JavaScript实现bind
  3. */
  4. Function.prototype.myBind = function(objThis, ...params) {
  5. const thisFn = this;//存储调用函数,以及上方的params(函数参数)
  6. //对返回的函数 secondParams 二次传参
  7. let funcForBind = function(...secondParams) {
  8. //检查this是否是funcForBind的实例?也就是检查funcForBind是否通过new调用
  9. const isNew = this instanceof funcForBind;
  10. //new调用就绑定到this上,否则就绑定到传入的objThis上
  11. const thisArg = isNew ? this : Object(objThis);
  12. //用call执行调用函数,绑定this的指向,并传递参数。返回执行结果
  13. return thisFn.call(thisArg, ...params, ...secondParams);
  14. };
  15. //复制调用函数的prototype给funcForBind
  16. funcForBind.prototype = Object.create(thisFn.prototype);
  17. return funcForBind;//返回拷贝的函数
  18. };

二次传参(secondParams)是说什么?

  1. let func = function(p,secondParams){//其实测试用的func其参数可以是任意多个
  2. console.log(p.name);
  3. console.log(this.name);
  4. console.log(secondParams);
  5. }
  6. let obj={
  7. name:"1891"
  8. }
  9. func.myBind(obj,{name:"coffe"})("二次传参");
  10. //>> coffe
  11. //>> 1891
  12. //>> 二次传参