Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

call、apply和bind的实现 #16

Open
Abiel1024 opened this issue Jul 20, 2018 · 9 comments
Open

call、apply和bind的实现 #16

Abiel1024 opened this issue Jul 20, 2018 · 9 comments

Comments

@Abiel1024
Copy link
Owner

Abiel1024 commented Jul 20, 2018

因为关乎到了this指向的问题,call、apply和bind的用法可以说是老生常谈了。这篇文章的主要作用是利用js原生方法对三个方法进行实现,升入了解其中的原理,对相关知识点有更好的掌握。

call与apply

简单介绍:call和apply方法都是使用一个指定的this值和对应的参数前提下调用某个函数或方法。区别则在于call是通过传多个参数的方式,而apply则是传入一个数组。
举个例子:

var obj = {
  name: 'linxin'
}

function func(age, sex) {
  console.log(this.name,age,sex);
}

func.call(obj,12,'女');         // linxin 12 女
func.apply(obj, [18, '女']);        //linxin 18 女

模拟实现

思路:在JavaScript中的this指向说到了:函数还可以作为某个对象的方法调用,这时this就指这个上级对象。也就是我们平时说的,谁调用,this就指向谁。所以实现的方法就是在传入的对象中添加这么一个方法,然后再去执行这个方法。为了保持对象一直,在执行完之后再把这个对象给删除了。是不是很简单^-^。
初体验

Function.prototype.newCall = function(context) {
  context.fn = this;  // 通过this获取call的函数
  context.fn();
  delete context.fn;
}
let foo = {
  value: 1
}
function bar() {
  console.log(this.value);
}
bar.newCall (foo); // 1

这样就完成了基础版本的实现,但是如果说有传参数呢?
所以我们可以进行优化一下,因为传入的参数数量是不确定的,所以我们可以从Arguments对象中去获取,这个比较简单。问题是参数是不确定的,我们如何传入到我们要执行的函数中去呢 ? 这里我们有两种选择:一种是通过eval拼接的方式,另一种就要用到es6了。
体验升级(eval版本):

Function.prototype.newCall = function(context) {
  context.fn = this;
  var args = [];
  for(var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
  }
  eval('context.fn(' + args +')');
  delete context.fn;
}
let person = {
  name: 'Abiel'
}
function sayHi(age,sex) {
  console.log(this.name, age, sex);
}
sayHi.newCall (person, 25, '男'); // Abiel 25 男

体验升级(ES6版本):

Function.prototype.newCall = function(context) {
  context.fn = this;  
  context.fn(...Array.from(arguments).slice(1));
  delete context.fn;
}
let person = {
  name: 'Abiel'
}
function sayHi(age,sex) {
  console.log(this.name, age, sex);
}
sayHi.newCall (person, 25, '男'); // Abiel 25 男

让然ES6的方法还可以不用到arguments就能实现
ES6版本再升级:

Function.prototype.newCall = function(context, ...parameter) {
  context.fn = this;  
  context.fn(...parameter);
  delete context.fn;
}
let person = {
  name: 'Abiel'
}
function sayHi(age,sex) {
  console.log(this.name, age, sex);
}
sayHi.newCall (person, 25, '男'); // Abiel 25 男

这样我们基本上实现了call的功能,但是还是存在一些隐患和区别。
当对象本身就有fn这个方法的时候,就有大问题了。
当call传入的对象是null的时候,或者其他一些类型的时候,函数会报错。
终极体验:

Function.prototype.newCall = function(context, ...parameter) {
  if (typeof context === 'object') {
    context = context || window
  } else {
    context = Object.create(null)
  }
  let fn = Symbol()
  context[fn] = this
  context[fn](...parameter);
  delete context[fn]
}
let person = {
  name: 'Abiel'
}
function sayHi(age,sex) {
  console.log(this.name, age, sex);
}
sayHi.newCall (person, 25, '男'); // Abiel 25 男

实现了call之后,apply也是同样的思路。
apply实现:

Function.prototype.newApply = function(context, parameter) {
  if (typeof context === 'object') {
    context = context || window
  } else {
    context = Object.create(null)
  }
  let fn = Symbol()
  context[fn] = this
  context[fn](...parameter);
  delete context[fn]
}

bind

bind也是函数的方法,作用也是改变this执行,同时也是能传多个参数。与call和apply不同的是bind方法不会立即执行,而是返回一个改变上下文this指向后的函数,原函数并没有被改变。并且如果函数本身是一个绑定了 this 对象的函数,那 apply 和 call 不会像预期那样执行。
初体验:

Function.prototype.bind = function (context) {
  var me = this
  return function () { // bind之后得到的函数
    return me.call(context)  // 执行是改变this执行
  }
}

加入参数:

Function.prototype.bind = function (context,...innerArgs) {
  var me = this
  return function (...finnalyArgs) {
    return me.call(context,...innerArgs,...finnalyArgs)
  }
}
let person = {
  name: 'Abiel'
}
function sayHi(age,sex) {
  console.log(this.name, age, sex);
}
let personSayHi = sayHi.bind(person, 25)
personSayHi('男')
@Abiel1024 Abiel1024 changed the title call、apply和bind的区别和实现 call、apply和bind的实现 Jul 22, 2018
@Lamborshea
Copy link

你好,你的 apply 实现代码有问题。

apply实现:

Function.prototype.newApply = function(context, parameter) {
  if (typeof context === 'object') {
    context = context || window
  } else {
    context = Object.create(null)
  }
  let fn = Symbol()
  context[fn] = this
  context[fn](parameter);  // 应该加上扩展运算符
  delete context[fn]
}

@Abiel1024
Copy link
Owner Author

@Lamborshea 已修正 谢谢~

@zhangyuexin
Copy link

感谢分享

@Abiel1024
Copy link
Owner Author

@zhangyuexin 不客气

@samualle
Copy link

samualle commented Sep 8, 2020

看了之后很喜欢,然后自己对 call 的最后实现稍微做了修改,欢迎各位也能指点指点。

Function.prototype.newCall = function (context, ...args) {
  if (context === null || context === undefined) {
    context = window;
  } else {
    context = Object(context);
  }
  let fn = Symbol();
  context[fn] = this;
  context[fn](...args);
  delete context[fn];
};
let person = {
  name: "Jim"
};
function sayHi(age, sex) {
  console.log(this.name, age, sex, this);
}
sayHi.newCall(person, 25, "male");

@Eleven-Ding
Copy link

看了之后很喜欢,然后自己对 call 的最后实现稍微做了修改,欢迎各位也能指点指点。

Function.prototype.newCall = function (context, ...args) {
  if (context === null || context === undefined) {
    context = window;
  } else {
    context = Object(context);
  }
  let fn = Symbol();
  context[fn] = this;
  context[fn](...args);
  delete context[fn];
};
let person = {
  name: "Jim"
};
function sayHi(age, sex) {
  console.log(this.name, age, sex, this);
}
sayHi.newCall(person, 25, "male");

我同意这个 传入null this直线window

@Eleven-Ding
Copy link

我自己对bind做了一下修改

 Function.prototype._bind = function (ctx) {
        const origin = this;
        const Mid = function () {};
        const args = [].slice.call(arguments, 1);
        const newFn = function () {
          const args1 = [...arguments].concat(args);
          return origin.apply(this instanceof newFn ? this : ctx, args1);
        };
        Mid.prototype = this.prototype;
        newFn.prototype = new Mid();
        return newFn;
      };

@chyun1006
Copy link

Function.prototype.myBind = function (context, ...args) {
            let self = this

            function fn(...args) {
                context.fun = self
                context.fun(...args)
            }

            return function () {
                return fn(...args)
            }
        }

@wind8866
Copy link

同意 @samualle 的观点,因为作者写的代码当传入 undefinedthis 应该指向 window,而作者的代码 this 指向了一个对象。

下面是我的实现

Function.prototype.fakeCall = function(target, ...params) {
    if (target == null) {// undefined || null
        target = window
    } else if (typeof target !== 'object') {
        target = Object.create(null)
    }
    const key = Symbol()
    target[key] = this
    target[key](...params)
    delete target[key]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants