如何实现JavaScript中new、apply、call、bind的底层逻辑
admin
2024-03-01 10:55:19
0

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 = namethis.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 = namethis.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'}//创建一个新对象const obj = {}// obj的原型指向构造函数的原型,这样obj就可以访问构造函数原型上的属性,原型链上的对象不能丢失obj.__proto__ = Object.create(ctor.prototype)//将构造函数的this指向obj,这样obj就可以访问构造函数的属性const res = ctor.apply(obj,argums)//这里用来弥补简洁版缺陷,这样就比较容易理解了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 = namethis.sex = sex
}
Fun.prototype.getName = function() {console.log('name:',this.name)
}
const fun = _new(Fun,1,2) //能拿到new 生成的对象
console.log('getName:',fun.getName) //能拿到构造函数上的原型方法

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.funreturn res
}

call

Function.prototype.call = function (context, ...args) {var context = context || window// this: 调用call 的函数,args,调用call的参数context.fun = thisconst res = context.fun(...args)delete context.funreturn res
}
obj.getName.call()

bind

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

Function.prototype.bind = function(context, ...args){var self = thisvar 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
}

总结

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

相关内容

热门资讯

暑假带孩子去贵州旅游5天4晚大... 暑假带孩子去贵州旅游5天4晚大概多少钱,贵州5天4夜的旅游价格,避坑指南。最近我终于下定决心,在暑假...
【仙境宽甸】逃离热浪!宽甸消夏... 希望这个夏天●西瓜甜一点 当全国进入“烧烤模式”,辽东的宽甸却像被遗忘的天然空调房!这里森林覆盖率7...
避坑指南:奶茶店照明设计中易被... 要知道对于奶茶店而言,照明设计对于店铺流量与销量的提升还是有着很大作用的,所以做好照明设计对于顾客体...
如何高效利用早晨阅读练习题目?... 早晨阅读练习题目是有效利用时间来增强学习成效的途径,然而,众多人并不清楚如何进行高效的操作。以下,我...
驴友川藏线偶遇一徒步00后大爷... 驴友川藏线偶遇一徒步00后大爷 :分手后想不开 出来徒步散散心 四川dou知道 西藏 00后
万源市文化体育和旅游局 户外露... 露营户外安全提示 广大游客朋友:  为了保证您和他人的安全,请选择具有正规资质的露营地露营,遵守好露...
【东非】:六驱猛兽+变形金刚+... 营地英文:Roving Bushtops 营地位置:坦桑尼亚,塞伦盖蒂 【东非顶级移动行宫】 ...
In-N-Out真的要凉了?“... 真是风水轮流转啊… In-N-Out的口碑和热度也是急转直下。 USA TODAY 最新发布的 “...
石岛渔港千帆竞发,镜头下的渔家... 荣成,山东半岛最东端的宝藏小城,三面被大海拥抱,坐拥壮丽的“天尽头”海景打卡点。这里既可以遇见嶙峋的...
陈麻花:重庆磁器口排队王,酥脆... 在重庆磁器口古镇,陈麻花无疑是最亮眼的美食名片之一,被誉为 “排队王”。它凭借酥脆的口感、独特的麻辣...
原创 推... “从手到口,从口到心,中国人延续着对世界和人生特有的感知方式。只要点起炉火,端起碗筷,每个平凡的人,...
原创 超... “从手到口,从口到心,中国人延续着对世界和人生特有的感知方式。只要点起炉火,端起碗筷,每个平凡的人,...
青岛市即墨区:联动多方力量,让... 近日,即墨区潮海街道联动多方力量开展“畅游青岛 文明相伴”主题活动,从机关表率到社区治理,从文明交通...
重磅发布丨庆祝西藏自治区成立6... 在西藏自治区成立60周年之际,西藏自治区党委宣传部、西藏广播电视台共同出品的大庆主题概念片《心约西藏...
游客投喂屡禁不止!记者探访上海... 上海动物园一岁半的棕熊"军军"被大家称为"西郊达菲",自出生以来深受游客喜爱。他原本活泼好动,但近期...
厦门旅游3天2晚最详细路线安排... 厦门旅游3天2晚最详细路线安排,南普陀寺玩三天估计消费多少 大家好呀,我是你们的导游小陈!厦门这座...
积极备战,蓄力全运!广东男篮结... 随着昨日广东男篮79-69战胜西班牙职业男篮明星队,近两周三站七场的热身赛征程告一段落。通过与各国劲...