今天看啥  ›  专栏  ›  fen同学

javascript之bind、apply、call、new

fen同学  · 掘金  ·  · 2019-10-05 14:33
    

文章预览

阅读 12

javascript之bind、apply、call、new

1、bind模拟实现

bind作用

bind() 方法会创建一个新函数。
当这个新函数被调用时,bind() 的第一个参数将作为它运行时的this,
之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
复制代码

1、返回新的函数

2、传递参数

第一步,返回新的函数

Function.prototype.bind1 = function(context){
    let self = this;
    return function (){
        return self.apply(context);
    }
}
复制代码

第二步,传递参数

Function.prototype.bind1 = function(context){
    let self = this;
    return function (){
        let args2 = Array.prototype.slice.call(arguments, 1);
        return self.apply(context, args2);
    }
}
复制代码

绑定的时候传递一部分参数,调用的时候传递一部分参数

Function.prototype.bind1 = function(context){
    let self = this;
    let args1 = Array.prototype.slice.call(arguments, 1);
    
    return function (){
        let args2 = Array.prototype.slice.call(arguments);
        return self.apply(context, args1.concat(args2));
    }
}
复制代码

第三步,构造函数的效果模拟

当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。

Function.prototype.bind1 = function(context){
    let self = this;
    let args1 = Array.prototype.slice.call(arguments, 1);
    
    let fBind = function (){
        let args2 = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fBind ? this: context, args1.concat(args2));
    }
    fBind.prototype = this.prototype;
    return fBind;
}
复制代码

第四步,构造函数的效果优化

但是在这个写法中,我们直接将fBind.prototype=this.prototype,我们直接修改 fBind.prototype的时候,也会直接修改绑定函数的prototype。这个时候,我们可以通过一个空函数来进行中转:

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

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

    var fNOP = function () {};

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

    fNOP.prototype = this.prototype;
    fBind.prototype = new fNOP();
    
    return fBind;
}
复制代码

2、call的模拟实现

call() 方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。

1、改变this指向

2、调用函数

第一步,改变this指向

模拟的步骤可以分为:

1)将函数设为对象的属性

2)执行该函数

3)删除该函数

Function.prototype.call1 = function(context){
    //this指向绑定函数
    //context指向传递的第一个参数
    
    context.fn = this;
    context.fn();
    delete context.fn;
}

var foo = {
    value: 1,
};
function bar(){
    console.log(this.value)
}

bar.call1(foo); //1
复制代码

第二步,传递参数

Function.prototype.call1 = function(context){
    //this指向绑定函数
    //context指向传递的第一个参数
    context.fn = this;

    let args = [];
    for(let i = 1, len = arguments.length; i < len; i++){
        // 执行后 args为 ["arguments[1]", "arguments[2]", "arguments[3]"]
        args.push('arguments['+ i +']');
    }
    eval('context.fn('+ args +')');

    delete context.fn;
}
var foo = {
    value: 1,
};
function bar(a, b){
    console.log(this.value, a ,b)
}
bar.call1(foo, 2, 3);//1,2,3
复制代码

第三步,有返回值,或this为null时指向window

Function.prototype.call1 = function(context){
    //this指向绑定函数
    //context指向传递的第一个参数
    context = context || window;
    context.fn = this;
    
    let args = [];
    for(let i = 1, len = arguments.length; i < len; i++){
        // 执行后 args为 ["arguments[1]", "arguments[2]", "arguments[3]"]
        args.push('arguments['+ i +']');
    }
    let result = eval('context.fn('+ args +')');
    delete context.fn;
    return result;
}
var foo = {
    value: 1,
};
var value = 2;
function bar(a, b){
    console.log(this.value, a ,b)
}
bar.call1(null, 2, 3);
复制代码

3、apply的模拟实现

类似于call,只是apply参数为数组

 Function.prototype.apply1 = function (context, arr) {
    //this指向绑定函数
    //context指向传递的第一个参数
    context = context || window;
    context.fn = this;
    let result;
    if(!arr){
        result = context.fn();
    } else {
        let args = [];
        for (let i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')');
    }

    delete context.fn;
    return result;
}
var foo = {
    value: 1,
};
var value = 2;
function bar(a, b){
    console.log(this.value, a ,b)
}
bar.apply1(null, [2, 3]);//2,2,3
复制代码

4、new的模拟实现

new的作用创建对象类型的实例

// Otaku 御宅族,简称宅
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 = new Otaku('Kevin', '18');

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60

person.sayYourName(); // I am Kevin
复制代码

从上面看出,实例person具可以:

1、访问到 Otaku 构造函数里的属性

2、访问到Otaku.prototype属性

第一步,基本实现

function objectFactory(){
    let obj = new Object();
    let Constructor = [].shift.apply(arguments);

    //访问原型对象上的属性
    obj.__proto__ = Constructor.prototype;
    //访问构造函数上的属性
    Constructor.apply(obj, arguments);

    return 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
复制代码

第二步,返回值的效果

第一种情况,构造函数的返回值是对象形式,只能访问返回的对象中的属性

第二种情况,返回值为基本类型,与没有返回值一样,正常使用一样能方法原型对象属性和构造函数属性

function objectFactory(){
    let obj = new Object();
    let Constructor = [].shift.apply(arguments);

    //访问原型对象上的属性
    obj.__proto__ = Constructor.prototype;
    //访问构造函数上的属性
    let ret = Constructor.apply(obj, arguments);
    
    return typeof ret === 'object'  ? (ret || obj) : obj;
}
复制代码

参考链接:github.com/mqyqingfeng…

………………………………

原文地址:访问原文地址
快照地址: 访问文章快照
总结与预览地址:访问总结与预览