call,apply,bind的区别

  • apply接收数组 func.apply(obj, [arus])
  • call一连串参数 func.call(obj, param1, param2....)
  • bind返回一个函数 func.bind(obj,param...)(parms...)

call,apply,bind的使用场景

  • 将类数组/含有length属性的对象转化为数组

    类数组:(例如通过document.getElementsByTagName获取的元素、含有length属性的对象)具有length属性,并且可以通过0、1、2…下标来访问其中的元素,但是没有Array中的push、pop等方法。

    注意:但是这个不适用于IE6~8,会报错,只能使用循环来解决

    // 类数组let trueArr = Array.prototype.slice.call(arrayLike)// 含有length属性的对象let obj4 = {    0: 1,    1: 'thomas',    2: 13,    length: 3 // 一定要有length属性};console.log(Array.prototype.slice.call(obj4)); // [1, "thomas", 13]
  • 求数组中的最大和最小值

    注意:边界问题,临界值大概在 [ 参数个数限制在65536]

    let arr = [1,2,3,89,46]let max = Math.max.apply(null,arr)//89let min = Math.min.apply(null,arr)//1
  • 数组追加

    数组方法contact比较:contact返回新数组,不修改原数组

    let arr1 = [1,2,3]let arr2 = [4,5,6]let total = [].push.apply(arr1, arr2) //6
  • 利用call和apply做继承

    function Person(name,age){    // 这里的this都指向实例    this.name = name    this.age = age    this.sayAge = function(){        console.log(this.age)    }}function Female(){    Person.apply(this,arguments)//将父元素所有方法在这里执行一遍就继承了}let dot = new Female('Dot',2)
  • 判断变量类型

    function isArray(obj){    return Object.prototype.toString.call(obj) == '[object Array]'}isArray([]) // trueisArray('dot') // false
  • 其他:使用 log 代理 console.log

    function log(){  console.log.apply(console, arguments);}// 当然也有更方便的 let log = console.log()

bind 实现

  • 特点:

    1. 返回一个函数
    2. 可以传入参数(使用bind时和bind新生成的函数都可以传参)
    3. 当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效

      var bindFoo = bar.bind(foo, 'daisy');var obj = new bindFoo('18');
  • 注意:bind这个方法在IE6~8下不兼容
// 使用apply和call来实现this指向问题Function.prototype.bind2 = function (context) {    if (typeof this !== "function") {      throw new Error("what is trying to be bound is not callable");    }    var self = this;   // 获得bind的参数从第二个参数到最后一个参数    var args = Array.prototype.slice.call(arguments, 1);    var fNOP = function () {};    var fBound = function () {        // 指bind返回的函数传入的参数        var bindArgs = Array.prototype.slice.call(arguments);       // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值        // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性        // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context        // new bind返回的函数,this失效,但传入的参数生效        return self.apply(this instanceof fNOP ? this : context,                                args.concat(bindArgs));    }        // fBound.prototype = this.prototype;    // 保证继承,原型链,让 fBound 构造的实例能够继承绑定函数的原型中的值,下面两行代码等同于Object.creater()  fbound.prototype = Object.create(this.prototype);    // 我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转    fNOP.prototype = this.prototype;    fBound.prototype = new fNOP();    return fBound;}// es6实现Function.prototype.bind = function(context) {    if(typeof this !== "function"){       throw new TypeError("not a function");    }    let self = this;    let args = [...arguments].slice(1);    function Fn() {};    Fn.prototype = this.prototype;    let bound = function() {        let res = [...args, ...arguments]; //bind传递的参数和函数调用时传递的参数拼接        context = this instanceof Fn ? this : context || this;        return self.apply(context, res);    }    //原型链    bound.prototype = new Fn();    return bound;}

call 实现

  • 实现思路

    1. 将函数设为对象的属性 foo.fn = bar
    2. 执行该函数 foo.fn()
    3. 删除该函数 delete foo.fn
  • 注意的点

    1. 接受不定长参数 - Arguments 对象中取值,第二个到最后一个参数,然后放到一个数组里
    2. this 参数可以传 null,当为 null 的时候,视为指向 window
    3. 函数是可以有返回值的!
  • 难点解析 - 接受不定长参数

    var args = [];// 为了拼出一个参数字符串,arguments类数组,不能使用for(var i = 1, len = arguments.length; i < len; i++) {      // args: ["arguments[1]", "arguments[2]", .....]    args.push('arguments[' + i + ']');}// 1. context.fn(args.join(',')) es6语法实现es3的call方法不合适// 2. 这里 args 会自动调用 Array.toString() 这个方法// 3. eval作用:看成是<script>标签,只接受原始字符串作为参数,用JavaScript的解析引擎来解析这一堆字符串里面的内容var result = eval('context.fn(' + args +')');
  • call整体实现

    Function.prototype.call2 = function (context) {  // 首先要获取调用call的函数,用this可以获取    var context = context || window;    context.fn = this;    var args = [];    for(var i = 1, len = arguments.length; i < len; i++) {        args.push('arguments[' + i + ']');    }    var result = eval('context.fn(' + args +')');    delete context.fn    return result;}// 测试bar.call2(null); // 2console.log(bar.call2(obj, 'kevin', 18));// es6Function.prototype.call = function (context) {    if (!context) {        context = typeof window === 'undefined' ? global : window;    }    context.fn = this;    let rest = [...arguments].slice(1);// 空数组slice后返回的仍然是空数组    let result = context.fn(...rest);     delete context.fn;    return result;}

apply实现

Function.prototype.apply = function (context, arr) {    var context = Object(context) || window;    context.fn = this;    var result;    if (!arr) {        result = context.fn();    }    else {        var args = [];        for (var i = 0, len = arr.length; i < len; i++) {            args.push('arr[' + i + ']');        }        result = eval('context.fn(' + args + ')')    }    delete context.fn    return result;}// es6:Function.prototype.apply = function (context, rest) {    if (!context) {        //context为null或者是undefined时,设置默认值        context = typeof window === 'undefined' ? global : window;    }    context.fn = this;    let result;    if(rest === undefined || rest === null) {        //undefined 或者 是 null 不是 Iterator 对象,不能被 ...        result = context.fn(rest);    }else if(typeof rest === 'object') {        result = context.fn(...rest);    }    delete context.fn;    return result;}

补充:

  • new实现
  • 继承和原型链知识