关于手写bind与new操作符的思考(含手写流程)

本文详细比较了JavaScript中bind(),call(),apply()的异同,强调了它们改变函数this指向、参数传递和返回结果的特点。并通过手写bind()函数,展示了如何创建绑定特定对象作用域的新函数以及new操作符在实例化中的作用。
摘要由CSDN通过智能技术生成

关于手写bind()与new操作符的思考

call(),apply(),bind()的异同

相同

改变函数运行时的this指向,好处是将任意对象设置为任意函数的作用域,这样对象可以不用关心方法

传入的第一个参数如果为nullundefined,则默认全局对象,在浏览器环境下是window对象,在node.js环境下是global对象(所以在手写时用globalThis指代全局对象,只写window是不够严谨的)

不同

传入的第二个参数:apply()传入数组或类数组,call()和bind()传入参数列表

参数传递次数:apply()和call()都是一次性传入参数,而bind()可以分多次传入

参数返回结果:apply()和call()都是立即执行函数,bind()返回绑定this对象后的函数

手写bind()

var a = 3, b = 4;        // 必须用var声明才会存储在变量对象中
// 定义函数
function sum(m, n){
    return this.a + this.b + m + n;
}
// 定义对象
let obj = {
    a : 1,
    b : 2
}
// 将obj的作用域绑定到sum函数中返回一个新函数newsum
let newSum = sum.bind(obj);
// 用新函数newsum构造函数创建实例对象o,newsum是函数,所以实例对象o也是函数
let o = new newSum(7,8);
// 不使用bind()的输出
console.log(sum(5,6));    // 18
console.log(newSum(5,6)); // 14
console.log(o);           // sum{}
思路
  1. bind()是函数原型上的方法,所有函数都可以使用,所以写在函数原型上

    // thisValue为传入的第一个参数,要绑定的对象
    Funtion.prototype.Mybind = function(thisValue){}
    
  2. 参数不合理处理:thisValue必须是引用类型,当其为null或undefined时,thisVal=globalThis,否则应该将原始类型转为引用类型

    Funtion.prototype.Mybind = function(thisValue){
        thisValue = thisValue === null || thisValue === undefined ? globalThis : Object(thisValue);
    }
    
  3. 由于bind()传入的是参数列表,所以使用arguments对象获取传入的参数更方便,由于bind()是可以分多次传参,所以要分别获取两次的参数列表后再进行拼接得到最终的参数列表,可以使用数组的slice()截取参数,concat()拼接参数

  4. 在bind()方法中,this对象是指调用bind()方法的函数,在上面的代码中sum函数调用bind()方法,所以在bind()方法中this指代sum函数

    Funtion.prototype.Mybind = function(thisValue){
        thisValue = thisValue === null || thisValue === undefined ? globalThis : Object(thisValue);
        let fn = this;
        // 第一次传参
        // arguments是类数组,没有slice()方法,所以要在slice()方法上使用call()使用arguments的作用域截取
        let args1 = Array.prototype.slice.call(arguments, 1);
    }
    
  5. bind()返回的是函数,这个函数是绑定thisValue作用域的函数,那么如何在原来的函数绑定新的作用域呢?只要我们对原来的函数使用apply()方法就可以实现在任意函数使用任意对象的作用域了

    Funtion.prototype.Mybind = function(thisValue){
        thisValue = thisValue === null || thisValue === undefined ? globalThis : Object(thisValue);
        let fn = this;
        let args1 = Array.prototype.slice.call(arguments, 1);
        // bind()方法返回函数,这个函数是使用了thisValue作用域的fn函数
        return function(){
            //这里写的内容不属于bind()方法了
            // 第二次传参
            let args2 = Array.prototype.slice.call(arguments);
            // 两次传参拼起来
            let args = args1.concat(args2);
            // 返回使用thisValue作用域的fn函数的返回值
    		return fn.apply(thisValue, args);
        }
    }
    
  6. 将bind()返回的函数理解成构造函数(构造函数就是一个函数),就可以使用new操作符创建构造函数的实例对象

  7. 在构造函数(bind()返回的函数)中,this指代的是构造函数创建的实例对象

    Funtion.prototype.Mybind = function(thisValue){
        thisValue = thisValue === null || thisValue === undefined ? globalThis : Object(thisValue);
        let fn = this;
        let args1 = Array.prototype.slice.call(arguments, 1);
        return function constr(){
            // 给构造函数取名为constr,这里是constr的内容,与bind()无关
            let args2 = Array.prototype.slice.call(arguments);
            let args = args1.concat(args2);
            // 构造函数创建的实例对象
            let obj = this;
    		return fn.apply(thisValue, args);
        }
    }
    
  8. new操作符在创建的过程中,将实例对象与构造函数的原型对象通过原型链连接起来,要理解这一步如何判断,首先要理解new在创建实例对象的过程,先来看手写new的代码

    function myNew(fn, ...args) {
        if (typeof (fn) !== 'function') {
            throw new Error('current params is not correct!');
        }
        let obj = {};                                   // 1.创建一个新对象
        obj.__proto__ = fn.prototype;                   // 2.将新对象与构造函数通过原型链连接
        let result = fn.apply(obj, args);               // 3.将构造函数中的this绑定到新对象上
                                                        // 4.执行构造函数内部的代码,通过apply调用构造函数
        return result instanceof Object ? result : obj; // 5.如果构造函数返回非空对象则返回该对象,否则返回新对象
    }
    

    将新函数newSum和创建的实例对象o结合这段手写new代码看一下
    首先传入的fn是newSum,新创建一个obj对象,将obj与newSum通过原型链连接,让构造函数newSum绑定obj的作用域,输出newSum函数在obj的作用域上的运行结果

    let newSum = sum.bind(obj);
    let o = new newSum(7,8);
    

    newSum也就是我们手写bind()中返回的构造函数constr,我们对constr使用new创建实例对象时,会先创建一个新对象,然后将新对象与constr通过原型链连接,然后返回constr.apply()的运行结果
    那也就是如果我们对bind()返回的函数使用了new,实例对象的__proto__属性就会等于构造函数constr的prototype

    Funtion.prototype.Mybind = function(thisValue){    
        thisValue = thisValue === null || thisValue === undefined ? globalThis : Object(thisValue);    
        let fn = this;    
        let args1 = Array.prototype.slice.call(arguments, 1);    
        return function constr(){               
            let args2 = Array.prototype.slice.call(arguments);        
            let args = args1.concat(args2);                
            let obj = this;
            // 判断是否使用了new操作符
            if (obj.__proto__ === constr.prototype) {
    			// 创建obj
            }
            // 没有使用
            else return fn.apply(thisValue, args);    
        }
    }
    
  9. 那么new操作符是如何创建实例对象的呢?这里我们需要知道,我们在bind()返回的函数constr中做的实际上就是让原来的函数fn使用thisValue的作用域运行,所以在创建实例对象这里我们实际上就是创建原来的函数fn的实例对象

    Funtion.prototype.Mybind = function(thisValue){    
        thisValue = thisValue === null || thisValue === undefined ? globalThis : Object(thisValue);    
        let fn = this;    
        let args1 = Array.prototype.slice.call(arguments, 1);    
        return function constr(){             
            let args2 = Array.prototype.slice.call(arguments);        
            let args = args1.concat(args2);              
            let obj = this;
            if (obj.__proto__ === constr.prototype) {
                return new fn(...args);
            }
            else return fn.apply(thisValue, args);    
        }
    }
    

    到这里就清晰了,我们在使用new操作符时是将实例对象与构造函数通过原型链连接,而为在创建实例对象时,实际上是在创建原来函数的实例对象,不需要使用到构造函数(构造函数起到一个判断是否使用了new操作符的作用)

手写bind()
Function.prototype.Mybind = function (thisValue) {
    thisValue = thisValue === null || thisValue === undefined ? globalThis : Object(thisValue);
    let fn = this;        
    let args1 = Array.prototype.slice.call(arguments, 1);
    return function constr() {
        let args2 = Array.prototype.slice.call(arguments);
        let args = args1.concat(args2);
        if (this.__proto__ === constr.prototype) {
            return new fn(...args);
            // new创建过程,手写代码
            // let obj = {};
            // obj.__proto__ = fn.prototype;
            // let res = fn.apply(obj, args);
            // return res instanceof Object ? res : obj;

        }
        else return fn.apply(thisValue, args);
    }
}
  • 19
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值