Skip to content

apply 、call 和 bind 的实现和基本用法 #2

Open
@xiaochengzi6

Description

@xiaochengzi6

apply 和 call 简单的用法和两者之间的区别

apply 和 call 作用一样都是为了动态的改变 this

var obj = {
    a: '1'
}
var a = '2'
function func (arr,brr) {
    ...
}
func() // '2'
func.apply(obj,[arr,brr]) // '1'
func.call(obj, arr, brr) // '2'

apply 接收一个参数数组,call 接收参数,参数有多少就传入多少。

bind

bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。‘
bind 的特点

  1. 返回一个新建的新函数
  2. 返回的函数以 bind 方法中第一个参数作为 this 其他参数都会传入进去

先来看一道题。

var altwrite = document.write;
altwrite("hello"); // Uncaught TypeError: Illegal invocation

this 丢失导致的问题

altwrite.call(document, 'hello')
altwrite.bind(document)("hello")

call 的特点

call 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

返回值取决于这个方法

实现 call

Function.prototype.myCall = function(context){
    if( !context ){ context = window }
    else{
        context = Object(context)
    }
    const specialPrototype = Symbol('特殊的属性')
    
    context[specialPrototype] = this;
    let arg = [...arguments].slice(1)
    let value = context[specialPrototype](...arg)
    delete context.fn
    return value
}

注意事项:

  1. 首先在指定 this 时传入值为空 则会绑定到 window 属性上。为了预防 context 是基本数据类型使用 context = Object(context)将它绑定到原始值的实例对象上。
  2. 避免函数名相同使用 Symbol 来创建了一个函数名
  3. 删去在这个对象上绑定的属性.
  4. 返回最后的结果

实现 apply

Function.prototype.myApply = function (context) {
    if( !context ){ context = window }
    else{
        context = Object(context)
    }
    const specialPrototype = Symbol('特殊的属性')
    
    context[specialPrototype] = this;
    function isArrayLike(o) {
        if (o &&                                    // o不是null、undefined等
            typeof o === 'object' &&                // o是对象
            o.length >= 0 &&                        // o.length为非负值
            o.length === Math.floor(o.length) &&    // o.length是整数
            o.length < 4294967296)                  // o.length < 2^32
            return true
        else
            return false
    }
  	let args = arguments[1];
    let result
    if(args) {
        if(!Array.isArray(args) && !isArrayLike(args)){
            throw new TypeError('第二个参数不是对象')
        }else {
            args = Array.from(args);
            // args  = args.Array.prototype.slice()
            result = context[specialPrototype](...args) // 展开数组,传递函数参数
        }
        
    }else{
        result = context[specialPrototype]()
    }
	delete context[specialPrototype];
    return result;
}

注意事项:

  1. apply接受第二个参数为类数组对象,要去判别是否是类数组 然后将其转化成数组

实现bind

bind 的特点

  1. 函数调用,改变this

  2. 返回一个绑定this的函数

  3. 接收多个参数

  4. 支持柯里化形式传参 fn(1)(2)

  5. 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

Function.prototype.mybind = function (context) {
    let self = this;
    let arg = [...arguments].slice(1)
    
    return function() {
        let newArg = [...arguments];
        return self.apply(context, arg.concat(newArg))
    }
}

我感到了我和这个语言有一层膜 这个膜经常会在一些特殊的场合下我能感受到它 如果我能突破它 我将功力大增。就好比上面的代码 看似没啥问题但

var foo = {
  value: 1
};
Function.prototype.mybind = function (context) {
  let self = this;
  //var args = Array.prototype.slice.call(arguments, 1);
  let arg = [...arguments].slice(1)
  return function() {
      let newArg = [...arguments];
      return self.apply(context, arg.concat(newArg))
  }
}


function bar(name,jj) {
  console.log(this.value)
  console.log(name);
}
var n = bar.mybind(foo,1);
var w = new n;
console.log(w) //  {}

最终修改版

拷贝源函数:

  • 通过变量储存源函数
  • 使用Object.create复制源函数的prototype给fToBind

返回拷贝的函数

调用拷贝的函数:

  • new调用判断:通过instanceof判断函数是否通过new调用,来决定绑定的context
  • 绑定this+传递参数
  • 返回源函数的执行结果
    [你能手写实现一个bind吗?][https://juejin.cn/post/6844903906279964686]
Function.prototype.mybind = function (objTHis, ...params) {
	const thisFn = this // 获取调用的函数 bar.mabind  this === bar
    let fToBind = function (...secondParams) {
        const isNew = this  instanceof fToBind // 查看是否是 new 调用了
        const context = isNew ? this : Object(objThis);  //Object(objThis) 这里包装了一下
        return thisFn.call(coontext, ...params, ...secondParams);
    }
    if(thisFn.prototype) {
        // 将调用这个bind的函数的prototype 赋值给新创建的fToBind函数。
        fToBind.prototype = Object.create(thisFn.prototype);
    }
}

Function.prototype.bind2 = function (context) {

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP(); 
    // 这里注意 这里是将 fBound 的原型指向了 别的地方 就算 被 new 之后 原型的再次改动也不会去影响到 this (绑定函数的原型链) 
    // fToBind.prototype = Object.create(thisFn.prototype);
    //和这一步有一样的效果
    return fBound;
}

参考

你能手写实现一个bind吗?

call 和 apply 的模拟实现

Metadata

Metadata

Assignees

No one assigned

    Labels

    JavaScriptJavaScript 语言的学习

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions