• 如何实现JavaScript中new、apply、call、bind的底层逻辑


    new原理介绍

    new 关键词的主要作用就是指向一个构造函数,返回一个实例对象。

    实现思路

    1、创建一个新对象

    2、将构造函数的作用域赋给新对象(this指向新对象)

    3、执行构造函数中的代码(为这个新对象添加属性)

    4、返回新对象

    如果不用new的话,返回的结果就是undefined

    function Person(){
        this.name = 'tom'
    }
    
    const p = Person()
    console.log(p) //undefined, 没有加new的情况下,函数只是普通函数的执行,this在默认情况下是指向window
    console.log(name) // 因此name就是 tom
    console.log(p.name) // Cannot read properties of undefined (reading 'name')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当构造函数中有个 return 一个对象的操作

    当构造函数最后return出来的是个与this无关的对象时,new 命令会直接返回这个新对象,而不是通过new执行步骤生成的this对象

    function Person() {
        this.name = 'tom'
        return {age:18}
    }
    const p = new Person()
    console.log(p) // {age:18}
    console.log(p.name) //undefined
    console.log(p.age) // 18
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当构造函数中 显示返回 return发的不是一个对象是一个基本数据类型,则会使用 new 生成的对象返回

    当构造函数return 返回的不是一个对象时,会根据new 关键词执行逻辑,生成新的对象(绑定this),最后返回出来

    function Person() {
        this.name = 'tom'
        return 'hello'
    }
    const p = new Person()
    console.log(p) // {name:'tom'}
    console.log(p.name) //tom
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    总结

    new 关键词执行之后总是返回一个对象,要么是实例对象,要么是return 语句指定的对象

    apply、call、bind 原理

    基本使用

    fun.call(thisArg, params1, params2,params3)
    fun.apply(thisArg, [params1,params2, params3])
    fun.bind(thisArg, params1, params2, params3)()
    
    • 1
    • 2
    • 3

    共同点和不同点

    都能改变函数 fun 的this 指向,call 与 apply 的不用在于传参不同,apply 的第二个参数为数组,而call 则是从第二个参数到第 N 个 都是给 func 传参;

    而bind 和 (call 、apply)又不同,bind 虽然改变了this的指向,但不是马上执行,返回的是一个待执行的函数,而这两个 ( call、apply) 是在改变了函数的this 指向后立马执行。

    通过代码深入理解

    a 对象有个 getName 的方法,B 对象也临时需要使用同样的方法,那么这时候为 B 对象借用 A 对象的方法,即可达到目的,又节省重复定义,节约内存空间

    let a = {
        name : 'tom',
        getName(msg) {
            return msg + this.name;
        }
    }
    const b = {
        name : 'zq'
    }
    
    console.log(a.getName('hello'))
    console.log(a.getName.call(b, 'hi ~'))
    console.log(a.getName.apply(b,['hio ~']))
    const name = a.getName.bind(b, 'hello -')
    console.log(name())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    通过改变 this 的指向,让 b 对象 可以直接使用 a 对象的 getName 方法。

    改变this指向应用场景

    Object.prototype.toString 来判断类型

    // 这就是借用 Object 的原型上的 toString 方法,最后用返回用来判断 传入的参数
    Object.prototype.toString.call()
    
    • 1
    • 2

    细讲分析类型判断

    Object.prototype.toString()  // '[object Object]'
    
    //Array 借用 Object 的原型链上的 toString()
    Array.prototype.toString() // ''
    Array.prototype.toStrings = Object.prototype.toString
    Array.prototype.toStrings() // '[object Array]'
    Object.prototype.toString.call([]) //'[object Array]'
    
    
    // Number 借用 Object 原型链上的 toString 方法
    Number.prototype.toString() // '0'
    Number.prototype.toStrings = Object.prototype.toString //将 Object 原型链上的 toString 借到 Number 上
    var num = 1
    num.toStrings() //'[object Number]'
    num.toString() // '1'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    类数组借用方法

    类数组不是真正的数组,所以没有数组类型上的自带的种种方法,所以我们就可以利用一些方法取借用数组的方法,比如数组的push方法

    const arrayLike = {
        0: 'java',
        1: 'script',
        length:2
    }
    Array.prototype.push.call(arrayLike, 'tom', 'zq')
    console.log(typeof arrayLike) // object
    console.log(arrayLike) //{0: 'java', 1: 'script', 2: 'tom', 3: 'zq', length: 4}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    获取数组的最大最小值

    let arr = [18,2,19,1,20,3,10,8]
    const max = Math.max.apply(Math, arr)
    const min = Math.min.apply(Math, arr)
    console.log(max) // 20
    console.log(min) // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    继承

    function Parent3() {
        this.name = 'parent3'
        this.play = [1,2,3]
    }
    Parent3.prototype.getName = function () {
        return this.name;
    }
    function Child3() {
        //将Parent3 函数执行的this指向 当前函数,也就是借用Parent3函数为 Child3生成属性
        Parent3.call(this)
        this.type = 'child3'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    new 的实现原理

    new 被调用做了哪些事:

    1、让实例可以访问私有属性;

    2、让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性;

    3、构造函数返回的最后结果是引用数据类型

    简洁版

    //简版
    //1、创建一个新对象
    //2、将构造函数的作用域赋给新对象(this指向新对象)
    //3、执行构造函数中的代码(为这个新对象添加属性)
    //4、返回新对象
    function _new (ctor, ...argums) {
       if(typeof ctor != 'function'){
     	throw 'ctor is not  function'
       }
       const obj = {}
      
      const res = ctor.apply(obj,argums)
       console.log(111111111,res,argums ,obj)
        return obj
    }
    //构造函数
    function Fun(name,sex){
        this.name = name
        this.sex = sex
    }
    Fun.prototype.getName = function() {
        console.log('name:',this.name)
    }
    const fun = _new(Fun,1,2) //能拿到new 生成的对象
    console.log('getName:',fun.getName) //undefined 无法拿到构造函数定义的原型方法
    /*
    * 核心解析:
    * ctor(...argums) //这样写的函数里的this指向是window,那函数执行创建的对象跟new没啥关系了,而是跟window挂钩了
    *
    * 利用apply改变构造函数 this的指向,obj借用ctor构造函数为自己创建属性
    */
    function Fun(name,sex){
        this.name = name
        this.sex = sex
    }
    Fun(1,2)
    console.log(name) // 1
    console.log(sex) // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    完整版

    简洁版缺陷:

    1、当前构造函数如果显示返回对象,没有使用显示返回的对象进行返回。

    2、当前构造函数如果显示返回 基本数据类型,没有做判断处理

    3、当前构造函数如果显示返回 函数,没有做返回处理

    4、只是单纯的创建对象,拿不到构造函数原型上的属性

    注意:

    new 关键词执行之后总是返回一个对象,要么是实例对象,要么是return 语句指定的对象

    //完整版
    //1、创建一个新对象
    //2、将构造函数的作用域赋给新对象(this指向新对象)
    //3、执行构造函数中的代码(为这个新对象添加属性)
    //4、返回新对象
    function _new (ctor, ...argums) {
       if(typeof ctor != 'function'){
     	throw 'ctor is not  function'
       }
        
       //1、创建一个新对象
       //const obj = {}
       // 2、obj的原型指向构造函数的原型,这样obj就可以访问构造函数原型上的属性,原型链上的对象不能丢失
       //obj.__proto__ = Object.create(ctor.prototype)
       
       //步骤1和步骤2可以优化成一个步骤
       const obj = Object.create(ctor.prototype)
       
      
      //3、将构造函数的this指向obj,这样obj就可以访问构造函数的属性
      const res = ctor.apply(obj,argums)
        
      //4、这里用来弥补简洁版缺陷,这样就比较容易理解了
      const isObject = typeof res === 'object' && res != null //判断new构造函数执行是否显式返回了对象
      const isFunction = typeof res === 'function' //判断new构造函数执行是否返回了函数
      return isObject || isFunction ? res : obj //如果即没有显式返回函数也没显式返回对象,则返回由new创建的函数
    }
    //构造函数
    function Fun(name,sex){
        this.name = name
        this.sex = sex
    }
    Fun.prototype.getName = function() {
        console.log('name:',this.name)
    }
    const fun = _new(Fun,1,2) //能拿到new 生成的对象
    console.log('getName:',fun.getName) //能拿到构造函数上的原型方法
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    其他知识点
    Object.create

    这个方法可能大部分只是用来创建一个对象,但它最重要的功能是用在创建对象的原型,也就是要继承的对象或属性、方法

    //proto 创建对象的原型,也就是要继承的对象
    //propertiesObject 也是一个对象,用于创建的对象进行初始化
    Object.create(proto,propertiesObject)
    
    //Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或方法
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    Object.create 原理

    基础版实现,顺带研究Object.create 的底层实现,按照api 根据自己思路实现,有不对的地方一起讨论

    Object.create = function(prop, description){
        if(typeof prop != 'object'){
            //对象原型只能是Object或null:未定义
            throw 'Object prototype may only be an Object or null: undefined'
        }
        if(description === null){
            //无法将未定义或null转换为对象
            throw ' Cannot convert undefined or null to object'
        }
        if(typeof description === 'object'){
            var keyArray = Object.getOwnPropertyNames(description)
            for(var i = 0; i < keyArray.length; i++){
                if(typeof description[keyArray[i]] !== 'object') {
                    //属性描述必须是对象:2
                    throw 'Property description must be an object: 2'
                }
            }
        }
        if(!description){
            var obj = new Object()
            obj.__proto__ = prop
        } else {
            description.__proto__ = prop
        }
        return obj || description
    }
    
    //该实现只支持这几种方法
    // 初始化对象:属性描述必须是对象:2
    Object.create({a:1},{a:1})
    // 无法将未定义或null转换为对象
    Object.create({a:1},null)
    // 对象原型只能是Object或null:未定义
    Object.create({a:1},null)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    apply、call、bind原理

    apply和call的实现基本一样,只不过参数不同而已

    apply

    Function.prototype.apply = function(context, args) {
        var context = context || window
        //this 调用apply的方法,args 调用apply的数组参数
        context.fun = this 
       	//将参数传进去即可
        const res = context.fun(...args)
        delete context.fun
        return res
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    call

    Function.prototype.call = function (context, ...args) {
        var context = context || window
        // this: 调用call 的函数,args,调用call的参数
        context.fun = this
        const res = context.fun(...args)
        delete context.fun
        return res
    }
    obj.getName.call()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    bind

    bind的实现思路基本和apply一样,但是返回结果不同,不需要立即执行,而是通过返回一个函数的方式将结果返回,通过执行这个结果,得到想要的执行效果

    Function.prototype.bind = function(context, ...args){
        
        var self = this
        var fun = function() {
            console.log('this',this) // window,
            console.log('argums',[...arguments])  // 返回函数执行的参数
            console.log('context:',context) //要改变this指向的对象
            console.log('self',self) //调用call的执行的函数
            console.log('this instanceof self :',this instanceof self)
            console.log(111111111111111111111)
            self.apply(this instanceof self ? this : context, args.concat([...arguments]))
        }
    
        if(this.prototype) {
            // 在返回fun的过程中,原型链上的对象不能丢失,所以需要在这里使用Object.create 将this.prototype 上的属性挂到 fun的原型上面
            fun.prototype = Object.create(this.prototype)
        }
        return fun
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    总结
    方法特征callapplybind
    方法参数多个参数单个数组多个
    方法功能函数调用改变this函数调用改变this函数调用改变this
    返回结果直接执行直接执行返回待执函数
    底层实现通过调用this拿到函数,接受参数执行通过调用this拿到函数,接受参数执行间接调用apply执行
  • 相关阅读:
    Linux高性能服务器编程——ch1笔记
    Redis代码实践总结(二)
    数据结构-单链表-力扣题
    如何在excel表中实现单元格满足条件时整行变色?
    NSS [NCTF 2018]滴!晨跑打卡
    vue3 props 传值
    C语言 ,不用string.h的函数,实现A+B A-B的字符串处理功能。
    Spring 的简单模拟实现
    【小5聊】纯javascript实现图片放大镜效果
    螺纹快速接头在卫浴行业中的应用提高产量降低生产成本
  • 原文地址:https://blog.csdn.net/qq_25286361/article/details/128100830