目录
1.浅拷贝和深拷贝的实现原理
- 浅拷贝:简单复制了变量的值(包括基本数据类型的值和引用数据类型的值都是直接复制)
- 深拷贝:基本数据类型的值直接复制,引用数据类型的值则根据这个引用数据类型对象生成一个新的对象,再将新对象的地址重新赋值。
区别:对于引用数据类型变量的值,浅拷贝直接将地址值复制过来,而深拷贝则是将原对象重新生成一个新对象,再将新对象的地址值复制过来。故浅拷贝的引用数据类型的变量指向的是同一个对象,而深拷贝则是分别指向不同的对象,改变其中一个对象不会影响另外一个对象。
// 对象数组的深拷贝
function deepClone(current) {
if (current === null) {
throw("请输入非空对象引用")
}
if (current instanceof Object) {
// 判断为对象还是数组
const target = ((current instanceof Array) ? [] : {})
// 遍历对象属性或者数组下标
for (let porperty in current) {
// 遍历对象自身属性而非原型身上的属性
if (current.hasOwnProperty(property)) {
// 判断是基本数据类型还是引用数据类型还是基本数据类型中的null
if (current[property] === null) {
target[property] = current[property]
} else if (typeof current[property]==="obj") {
target[property]=deepClone(current[property])
}else{
target[property] = current[property]
}
}
}
return target
} else {
throw("请输入数组或者对象")
}
}
// 对象数组的浅拷贝
function shallowClone(current) {
if (current === null) {
throw("请输入非空对象引用")
}
if (current instanceof Object) {
const target = ((current instanceof Array) ? [] : {})
for (let porperty in current) {
if (current.hasOwnProperty(property)) {
target[property] = current[property]
}
}
return target
} else {
throw("请输入数组或者对象")
}
}
2.apply,call,bind的实现原理
注意:都是函数Function的原型上的方法,供函数实例使用。
相同点:三者都可以改变函数中的this指向
区别:call可以接受多个参数,apply只能接受两个参数,且如果有第二个参数则必须是数组形式的,且call和apply的第一个参数都是改变后this的指向对象,后面的参数将作为实参传递给原函数执行。。bind与call和apply有些不一样,bind是返回一个改变了this的函数,而接受参数的方式和call类似,只不过返回的函数我们同样也可以传递多个参数,都作为实参传递给原函数执行。
Function.prototype.call=function(obj,...arg){
// 如果传入null默认是window对象
current=current||window
//将函数保存在穿进来的obj对象身上,谁调用call,函数实例调用call,那么this就是那个函数实例
obj.p=this
// 执行函数,这个时候函数的this就是obj了
let result=obj.p(...arg)
// 将在obj对象身上的添加的函数属性删除
delete obj.p
// 返回函数的执行结果
return result
}
Function.prototype.apply=function(obj,arg){
// 如果传入null默认是window对象
current=current||window
//将函数保存在穿进来的obj对象身上,谁调用call,函数实例调用call,那么this就是那个函数实例
obj.p=this
// 执行函数,这个时候函数的this就是obj了
let result=obj.p(...arg)
// 将在obj对象身上的添加的函数属性删除
delete obj.p
// 返回函数的执行结果
return result
}
Function.prototype.bind=function(obj,...arg){
// 保存this,返回的函数可以new操作,故返回的函数不能new,所以要保存this变量
let that=this
return function(...arg1){
// 闭包obj,arg,that
that.call(obj,...arg,...arg1)
}
}
注意:
- bind返回的函数可以通过new的形式调用,故不能通过箭头函数的形式返回,所以这个时候里面的this就失效了,故在外面要保存this的值。
3.防抖节流函数实现原理。
防抖(debounce):事件触发过于频繁,只要执行最后一次事件的回调函数的操作
节流(throttle):事件触发过于频繁,控制事件的执行次数。
// 节流函数
function throttle(fun, delay) {
// 闭包flag
// 为什么这里要用到闭包,不然每次执行事件,
// 都会执行下面的函数,都会出现一个新的falg=true,这样就没有任何意义了
let falg = true
return function () {
if (flag) {
setTimeout(() => {
// 事件回调函数的this是指向触发事件的那个对象的
fun.call(this)
flag = true
}, delay)
}
flag = false
}
}
// 防抖函数
function debounce(fun, delay) {
let timer
// 为什么这里要用到闭包,不然每次执行事件,都会执行下面的函数,都会出现一个新的timer
return function () {
if (timer) {
clearInterval(timer)
}
timer = setTimeout(() => {
fun.call(this)
}, delay)
}
}
注意:
- 防抖和节流里面都用到了闭包,一个是定时器的timer,一个是标记变量flag,如果在返回中的变量中定义这两个变量的话,那么每次都会对这两个变量进行初始化赋值,这样我们就不知道上一次操作中flag和timer的具体情况了,但是我们要判断上一次timer和flag的具体状态,然后来进行不同的操作。所以不能写在返回的函数中,应写在外部函数中,然后内部函数可以对其进行更改,下一次函数调用的时候还是保存的更改后的值,这就是闭包的利用。
- 返回的函数中的fun()执行的时候是window.fun()这样执行的,所以this肯定是window,但是里面的this应该是返回函数中的this,也是触发这个时间的对象,所以这里要用call改变fun函数的this指向。
4.new关键字的实现原理
知道原理之前我们先看一下new关键都实现了什么?
- 创建一个空对象
- 将空对象的隐式原型指向构造函数的显式原型
- 将空对象作为构造函数的上下文(改变this指向)
- 对构造函数做有返回值的处理判断
// 其中fun为构造函数
function Mynew(fun,...arg) {
// 创建一个空对象
let obj = {}
// 将空对象的隐式原型指向构造函数的显式原型
Object.setPrototypeOf(obj,fun.prototype)
// 将空对象作为构造函数的上下文(改变this指向)
let result=fun.apply(obj,arg)
// 对构造函数做有返回值的处理判断tance
return result instanceof Object?result:obj
}
注意:
- 在返回对象的时候内部做了一个判断,如果构造函数return的是基本数据类型(包括null),那么对返回的结果没有任何影响,如果返回的是一个对象的话,那么则返回的结果则变成了我们自己返回的这个对象(包括数组,对象,函数)。
- 此处设置原型用的是Object的静态方法setPrototypeOf(),Object还有一个静态方法getPrototypetyOf()用户获取原型对象。
非常感谢您的阅读,欢迎大家提出您的意见,指出相关错误,谢谢!越努力,越幸运!