如何实现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')

当构造函数中有个 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

当构造函数中 显示返回 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

总结

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

apply、call、bind 原理

基本使用

fun.call(thisArg, params1, params2,params3)
fun.apply(thisArg, [params1,params2, params3])
fun.bind(thisArg, params1, params2, params3)()

共同点和不同点

都能改变函数 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())

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

改变this指向应用场景

Object.prototype.toString 来判断类型

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

细讲分析类型判断

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'

类数组借用方法

类数组不是真正的数组,所以没有数组类型上的自带的种种方法,所以我们就可以利用一些方法取借用数组的方法,比如数组的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}

获取数组的最大最小值

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

继承

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'
}

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、只是单纯的创建对象,拿不到构造函数原型上的属性

注意:

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) //能拿到构造函数上的原型方法

其他知识点

Object.create

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

//proto 创建对象的原型,也就是要继承的对象
//propertiesObject 也是一个对象,用于创建的对象进行初始化
Object.create(proto,propertiesObject)

//Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或方法

如何实现JavaScript中new、apply、call、bind的底层逻辑_第1张图片

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)

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
}

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()

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
}

总结

方法特征 call apply bind
方法参数 多个参数 单个数组 多个
方法功能 函数调用改变this 函数调用改变this 函数调用改变this
返回结果 直接执行 直接执行 返回待执函数
底层实现 通过调用this拿到函数,接受参数执行 通过调用this拿到函数,接受参数执行 间接调用apply执行

你可能感兴趣的:(javascript,javascript,前端,开发语言)