详解call bind apply区别/使用场景/es6实现/es3实现

更新日期: 2019-04-25阅读: 2.7k标签: 区别

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)//89
let 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([]) // true
isArray('dot') // false

其他:使用 log 代理 console.log

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


bind 实现

特点:

    返回一个函数

    可以传入参数(使用bind时和bind新生成的函数都可以传参)

    当 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 实现

实现思路

    将函数设为对象的属性 foo.fn = bar

    执行该函数 foo.fn()

    删除该函数 delete foo.fn

注意的点

    接受不定长参数 - Arguments 对象中取值,第二个到最后一个参数,然后放到一个数组里

    this 参数可以传 null,当为 null 的时候,视为指向 window

    函数是可以有返回值的!

难点解析 - 接受不定长参数

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); // 2
console.log(bar.call2(obj, 'kevin', 18));

// es6
Function.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实现

继承和原型链知识


原文来自:https://segmentfault.com/a/1190000018977507


链接: https://fly63.com/article/detial/3049

AR / MR / VR / XR有什么区别?

VR能让人完全沉浸在虚拟环境中;AR能创建一个叠加在虚拟内容的世界,但不能与真实环境交互; MR则是虚拟与现实的混合体,它能创造出可以与真实环境交互的虚拟物体。最后,XR则是包括三种“现实”(AR,VR,MR)的术语。

理解screenX,clientX,pageX,offsetX,pageXoffset的区别

event.screenX、event.screenY鼠标相对于用户显示器屏幕左上角的X,Y坐标。标准事件和IE事件都定义了这2个属性,event.clientX、event.clientY鼠标相对于浏览器可视区域的X,Y坐标

css之word-wrap和word-break的区别

对于英文单词,如果有一个连写且长度很长的英文单词,在第一行显示不下的情况下,浏览器默认不会截断显示,而是把这个单词整体挪到下一行。但是当整体挪到下一行还是显示不完全该肿么办呢?

url 、src 、href 的区别

URL统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL

Js插件、 组件、类库、框架的区别

框架和类库等概念的出现都是源于人们对复用的渴望。“不要重复发明轮子”,成了软件界的一句经典名言。从最初的单个函数源代码的复用,到面向对象中类的复用(通常以类库的形式体现)

*.min.js跟*.js的区别

js是JavaScript 源码文件, .min.js是压缩版的js文件。减小体积 .min.js文件经过压缩,相对编译前的js文件体积较小,传输效率快。防止窥视和窃取源代码

PTN与SDH的区别?

SDH是基于TDM技术,主要用于传输语音,此外采用GFP封装来传输IP包,物理介质为光纤。PTN是采用DWDM技术,主要用于传输IP包、以太网帧,此外采用MPLS-TP技术来实现PWE3伪线

html中src与href的区别

写代码的时候就经常把这两个属性弄混淆,到底是href还是src,href表示超文本引用,用在link和a等元素上,href是引用和页面关联,是在当前元素和引用资源之间建立联系,src表示引用资源,表示替换当前元素,用在img,script,iframe上

Js绑定事件的两种方式的区别

运行后发现,点击后src没有变化,调试发现,这里this是window对象,而不是img标签对象。顿时感觉有点迷惑,因为以前绑定事件中,拿标签属性都是用的this,怎么这里不对了?

CSS中inline、block和inline-block的区别

block块级元素特点:每个块级元素都从新的一行开始,并且其后的元素也另起一行;inline内联元素特点:和其他元素都在一行上;元素的高度、宽度及顶部和底部边距不可设置;

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!