「小记」call 、apply、bind、new 的模拟实现

416 阅读3分钟

1. call

传入任意个参数,立即执行

使用一个指定的 this 值和若干指定的参数值的前提下,调用某个函数或方法:

var foo = { value : 1}
function bar(){
    console.log(this.value)
}
bar.call(foo)// 1
// call 改变了 this 的指向,指向了 foo;
// bar 函数执行了;

实现指定的 this 值,模拟的步骤分为:

  • 将函数设为对象的属性;
  • 执行该函数;
  • 删除该函数;
Function.prototype.call = function(context){
    // 获取调用 call 的函数,用 this 可以获取
    console.log(this)// bar(){ console.log( this.value ) }
    context.fn = this
    context.fn()
    delete context.fn
}
// 测试一下
var foo = { value : 1 }
function bar(){
    console.log(this.value)
}
bar.call(foo)// 1,相当于foo.bar()

实现若干指定的参数值:

  • 从 Arguments 对象中取值,取出第二个到最后一个参数,放到一个数组里
Function.prototype.call = function(context){
    context.fn = this
    
    var args = [...arguments].slice(1)
    context.fn(...args)
    
    delete context.fn
}
// 测试一下
var foo = { value : 1 }
function bar(name,age){
    console.log(this.value)
    console.log(name)
    console.log(age)  
}
bar.call(foo,'zouyan','18')
// 1
// zouyan
// 18

优化:

  • this 参数可以传 null ,当为 null 时,视为指向 window;
  • 函数可以有返回值;
Function.prototype.call = function(context) {
  var context = context || window
  context.fn = this
  var args = [...arguments].slice(1)
  var result = context.fn(...args)
  delete context.fn
  return result
}
// 测试一下
var value = 2
var foo ={ value : 1 }
function bar(name,age){
    console.log(this.value)
    return{
        value: this.value,
        name: name,
        age:age
    }
}
bar.call(null)// 2
bar.call(foo,'zouyan',18)// {value:1,name:'zouyan',age:'18'}

2. 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 = arr || []
    result = context.fn(...args)
  }
  delete context.fn
  return result
}
// 测试一下
var value = 2
var foo ={ value : 1 }
function bar(name,age){
    console.log('value',this.value)
    return{
        value: this.value,
        name: name,
        age:age
    }
}
bar.apply(null)
bar.apply(foo,['zouyan',18])// {value:1,name:'zouyan',age:'18'}

3. bind

传入参数,返回函数,需要手动执行

  • bind() 方法会创建一个新函数;
  • 传入 bind 的第一个参数作为它运行时的 this;
  • 传入的其他参数作为函数运行时的参数;
  • 两个特点:返回一个函数,可以传入参数;
// 模拟返回函数:
Function.prototype.bind = function(cxt){
    var self = this
    return function(){
        return self.apply(cxt)
    }
}
// 模拟传参
Function.prototype.bind = function(cxt){
    var self = this
    // 获取 bind 函数从第二个参数到最后一个参数
    var args = [].slice.call(arguments, 1)
    return function(){
        // arguments 指 bind 返回的函数传入的参数
        var newArgs = args.concat([].slice.call(arguments))
        return self.apply(cxt, newArgs)
    }
}

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

// 模拟实现构造函数
Function.prototype.bind = function(cxt){
    var self = this
    var args = [].slice.call(arguments, 1)
    var fNOP = function(){}
    var fBound = function(){
        var newArgs = args.concat([].slice.call(arguments))
        return self.apply(this instanceof fNOP ? this : cxt, newArgs)
    }
    fNOP.prototype = this.prototype
    fBound.prototype = new fNOP()
    return fBound
}
// 调用 bind 函数,不是函数则报错
// 兼容
Function.prototype.bind = function(cxt){
    if(typeof this !== "function"){
        throw new Error("is not a function")
    }
    var self = this
    var args = [].slice.call(arguments, 1)// 获取第二个参数到最后一个参数
    var fNOP = function(){}
    var fBound = function(){
        var newArgs = args.concat([].slice.call(arguments)) 
        return self.apply(this instanceof fNOP ? this : cxt, newArgs)
    }
    fNOP.prototype = this.prototype
    fBound.prototype = new fNOP()
    return fBound
}

4. new

  • 实例可以访问到构造函数里的属性;
  • 实例可以访问到构造函数原型中的属性;

实现步骤:

  • ① 建立一个新对象 obj :因为 new 的结果是一个新对象;
  • obj._proto_ 属性指向构造函数的 prototype :因为实例需要访问原型上的属性;
  • ③ 使用 apply 为 obj 添加新的属性:因为 obj 具有构造函数里的属性;
  • ④ 判断返回值是不是一个对象,是对象返回对象,否则返回 obj :构造函数可能有返回值;
// 模拟实现 new 关键字
function objectFactory() {
    // 步骤 ①
    var obj = new Object(),//存疑,为什么模拟new里面使用new?
    //var obj = Object.create(null),//报错:TypeError
    //var obj = {},//正常
    //var obj = Object.create({}),//正常
    //var obj = Object.create(Object.prototype),//正常
    
    // 步骤 ②
    // 获取构造函数,截取第一个参数;
    // shift 会修改原数组,所以 arguments 会被去除的一个参数;
    Constructor = [].shift.call(arguments);// 等同于 Array.prototype.shift.call(arguments)
    obj.__proto__ = Constructor.prototype; 
    
    var ret = Constructor.apply(obj, arguments); // 步骤 ③
    return typeof ret === 'object' ? ret||obj : obj; // 步骤 ④
};
// 构造函数
function Otaku (name, age) {
    this.name = name;
    this.age = age;
    this.habit = 'Games';
}
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}
// 模拟创建实例
var person = objectFactory(Otaku, 'Kevin', '18')

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60
person.sayYourName(); // I am Kevin

参考链接 :github.com/mqyqingfeng…