不知道有没有跟我一样每次面试前都要去现找一些题去看的小伙伴,基于这一痛点呢我想还是总结一下,这样显得更有效率也更加系统!写这篇文章也是为了准备面试所以也会尽可能写的详细一点。
遍历对象的方法有哪些
- for in
- Object.keys()
- Object.values()
- Object.entries()
- Reflect.ownKeys()
for in
该方法会遍历对象中的所有可枚举属性,也就是说即使是在原型中的可枚举属性也会被遍历到。
const obj={
a:1,
b:2,
c:3,
[Symbol('s')]:6
}
obj.__proto__.d=4;
obj.__proto__.f=5;
for(const key in obj){
console.log(key) // a,b,c,d,f
}
Object.keys()
该方法会获取对象自身可枚举属性的键名(属性名),并以数组的形式返回这些键名。
// ...重复部分省略
console.log(Object.keys(obj)); // ['a','b','c']
Object.values()
该方法会获取对象自身可枚举属性的属性值,并以数组的形式返回这些值。
// ...重复部分省略
console.log(Object.values(obj)); // [1,2,3]
Object.entries()
其实 keys、values、entries 这三个方法都是相对的,keys 用于获取键,values 用于获取值,那entries 当然就是用来获取对象的键值对形式的了。
// ...重复部分省略
console.log(Object.entries(obj)); // [['a',1],['b',2],['c',3]]
Reflect.ownKeys()
该方法会以数组的方式返回对象自身的所有属性包括不可枚举属性。
// ... 重复部分省略
console.log(Reflect.ownKeys(obj)) // ['a','b','c','Symbol(s)']
一、手写new方法
使用new生成对象实例总共经历了以下四个步骤:
- 创建一个新的对象;
- 将新对象的
__proto__属性指向构造函数的prototype; - 执行构造函数,并把this指向新创建的对象;
- 若有返回值则判断是否为引用类型,如果是则返回引用类型的值,否则返回新创建的对象(null虽然也是对象,但仍然会返回实例对象);
function mynew(){
// 创建一个新的对象
const obj = {};
// 从参数中获取构造函数
const func = [].shift.call(arguments);
obj.__proto__ = func.prototype;
// 执行构造函数,并把this指向新创建的对象
const res = func.apply(obj,[...arguments])
// 判断返回值类型如果为引用类型则返回对应的引用类型的值,否则返回新创建的对象
return res instanceof Object ? res : obj
}
// demo
function Person(name,age){
this.name = name;
this.age = age;
// return {name:'邓紫棋',age:18}
}
Person.prototype.fn = function(){
console.log("我可以陪你去流浪~~~")
}
const p = mynew(Person,'薛之谦',20) // {name:'薛之谦',age:20}
p.fn(); // 我可以陪你去流浪~~~ 如果Person中return的是引用类型则没有fn方法
二、手写instanceof方法
instanceof是用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,也可以用来检测一个实例对象是由哪个构造函数构造出来的。
function myInstanceof(obj,constructor){
// 如果是传入的是基本数据类型直接返回false
if(typeof obj != 'object' || obj == null)return false;
let proto = obj.__proto__; // 也可以使用Object.getPrototypeOf()方法获取对象原型
//
while(true){
// 已经查到了最顶层
if(proto == null)return false;
// 说明在原型链上
if(proto == constructor.prototype)return true;
// 再查找到顶层对象之前依然没有找到则继续向上查找
proto = proto.__proto__;
}
}
console.log(myInstanceof({},Object)); // true
console.log(myInstanceof({},Array)); // false
三、手写call、apply、bind方法
三个方法的作用相同:改变函数运行时的this值。
三个方法的对比:
call和bind方法的参数是一个参数列表,apply方法的参数是一个数组call和apply方法会立即执行函数,bind会返回一个新的函数,需要手动调用此函数才会获得执行结果
call
// 为了让所有函数都能调用到我们自定义的方法,需要把方法定义到Function的原型上
// myCall方法中的this指向fn函数,thisArg指向的是目标对象
Function.prototype.myCall = function(thisArg,...args){
// 如果传入的目标对象是undefined或null,就将this指向全局对象
thisArg = thisArg || window
// 目的是让fn成为目标对象的方法来运行,这样this便指向了目标对象(核心思路:根据谁调用函数this就指向谁的原则)
thisArg.f = this;
// 运行这个方法并传入剩余参数
let result = thisArg.f(...args)
// 返回值同fn原函数一样
return result;
/**
* 到这里call的基本功能就完成了,但还存在一些问题:
* 目标对象上会永远存在我们自定义的f属性,并且如果多个函数调用这个方法,而目标对象也相同,
则存在目标对象的f属性被覆盖的可能
* 我们可以通过以下两种方式解决:
* 1、使用Symbol数据类型来定义对象的唯一属性名
* 2、使用delete操作符删除对象中的某个属性
* (一下代码与上面没有关联)
*/
// 如果传入的目标对象是undefined或null,就将this指向全局对象
thisArg = thisArg || window
// 生成唯一属性名,解决覆盖的问题
const prop = Symbol();
// 注意这里不能使用.
thisArg[prop] = this;
// 运行这个方法,传入剩余参数,同样不能用.
let result = thisArg[prop](...args);
// 运行完删除属性
delete thisArg[prop]
return result;
}
// demo
function fn(name,age){
console.log(name,age); // '薛之谦',20
return '我的心愿是世界和平!'
}
let p = fn.myCall(obj,'薛之谦',20)
console.log(p); // 我的心愿是世界和平!
apply
apply和call的实现思路一样,只是传参方式不同
Function.prototype.myApply = function (thisArg, args) {
thisArg = thisArg || window;
// 判断是否接收参数,若未接收参数,替换为[]
args = args || []
const prop = Symbol();
thisArg[prop] = this;
let result = thisArg[prop](...args);
delete thisArg[prop];
return result;
}
bind
bind相对来说就比较复杂一点了,我们一点点来。
1、bind方法会返回一个改变this指向后的新方法。
// 原bind方法改变this指向demo
const name = "柚子";
const obj = {
name:"刑天铠甲"
}
function person(){
console.log(this.name)
}
person(); //柚子
const p = person.bind(obj)
p() //刑天铠甲
//v1 首先根据这一特点来实现第一版的bind方法
Function.prototype.myBind = function(thisArg){
// 获取调用bind的函数,也就是绑定函数
let self = this;
// 返回一个用来改变this指向的函数
return function(){
self.apply(thisArg);
}
}
注意点(当然也可以用箭头函数)为什么要在return前定义self来保存this?因为我们需要利用闭包将this(即person)保存起来,使得myBind方法返回的函数在运行时的this值能够正确地指向person。
const p = function(){
return this.apply(thisArg) // 若不用self保存this此时this指向window
}
p();
2、bind方法函数的参数可以分多次传入,即可以在bind中传入一部分参数,在执行返回的函数的时候,再传入另一部分参数。
// 原bind方法分段传参demo
const name = "柚子";
const obj = {
name: "刑天铠甲"
}
function person(age, sex, job) {
console.log(this.name, age, sex, job)
}
person(20, 'female', '藏修者') //柚子 20 female 藏修者
const p = person.bind(obj, 20, 'male')
p('消灭幽冥魔!'); //刑天铠甲 20 male 消灭幽冥魔!
// v2 分段接受参数
Function.prototype.myBind = function(thisArg){
// 获取调用bind的函数,也就是绑定函数
let self = this;
//用slice方法取第二个到最后一个参数(获取除this对象以外的所有参数)
let args = [...arguments].slice(1);
// 返回一个用来改变this指向的函数
return function(){
// 这里的arguments获取的是指向bind方法返回的函数中传入的参数
let innerArgs = [...arguments];
self.apply(thisArg,args.concat(innerArgs));
}
}
3、bind方法返回函数中的返回值。
// 原bind方法返回值demo
const name = "欢迎";
const obj = {
name: "飞影铠甲"
}
function person(age, sex, job) {
return {
name: this.name,
age,
sex,
job
}
}
const huan = person(22, 'female', 'leader')
console.log(huan) //{name: '欢迎', age: 22, sex: 'male', job: '打败幽冥魔'}
const fei = person.bind(obj, 22, 'male');
const res = fei('打败幽冥魔');
console.log(res) //{name: '飞影铠甲', age: 22, sex: 'male', job: '打败幽冥魔'}
// v3 获取函数的返回值
Function.prototype.myBind = function(thisArg){
// 获取调用bind的函数,也就是绑定函数
let self = this;
//用slice方法取第二个到最后一个参数(获取除this对象以外的所有参数)
let args = [...arguments].slice(1);
// 返回一个用来改变this指向的函数
return function(){
// 这里的arguments获取的是指向bind方法返回的函数中传入的参数
let innerArgs = [...arguments];
// 拼接bind方法传入的参数和bind方法返回的函数中传入的参数,统一在最后通过apply执行
// 返回一下执行结果即可
return self.apply(thisArg,args.concat(innerArgs));
}
}
四、手写Promise对象
Promise 是异步编程的一种解决方案,首先我们要想知道promise都有哪些特点
- promise对象的状态不受外界影响,promise对象代表一个异步操作并且有三种状态:
pending,fulfilled和rejected,只有异步操作的结果可以决定当前是哪一种状态并且状态不可逆; - promise执行的过程中如果出现报错,那么最后的状态也将会是
rejected; then方法的第一个参数是fulfilled状态的回调,第二个参数是rejected状态的回调函数,它们都是可选的,并且会返回一个新的Promise实例
const p = new Prmoise((resolve,reject)=>{
resolve(123);
})
以上是promise的基本用法,下面我们用class来实现。
// 首先我们可以看到Promise接受一个函数作为参数并且函数的参数也是一个函数,于是
// 我们可以这样写
class myPromise{
constructor(executor){
// 同时这里我们还可以获取到resolve和reject方法调用后传递的参数
const resolve=(data)=>{};
const reject=(err)=>{};
executor(resolve,reject)
}
}
现在我们已经把promise的基本形式写出来了,下面就开始实现它的一些语法特点。
1、改变promise的状态并且状态改变后不可逆
// promise状态的改变是在resolve或reject方法调用后才会改变的,所以
// 在实现这一操作需要在定义好的resolve和reject方法中来实现
// 首先我们需要声明一个存储状态的变量,由于这个变量实例对象并不需要用到
// 所以我们可以为当前的类设置一个私有属性
// 下面不再一一说明 #+属性名是ES6提供的类的私有属性
// 详见:https://es6.ruanyifeng.com/#docs/class#%E7%A7%81%E6%9C%89%E6%96%B9%E6%B3%95%E5%92%8C%E7%A7%81%E6%9C%89%E5%B1%9E%E6%80%A7
class myPromise{
#state = 'pending'
constructor(executor){
const resolve = (data) => {
this.#state = 'fulfilled'
};
const reject = (err) => {
this.#state = 'rejected'
};
executor(resolve,reject)
}
}
// 以上直接将状态赋值state变量的方式成为硬编码,后面我们会多次用到这些状态,
// 所以我们可以将他们定义成常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class myPromise {
#state = PENDING;
constructor(executor) {
const resolve = (data) => {
this.#state = FULFILLED;
};
const reject = (err) => {
this.#state = REJECTED;
};
executor(resolve, reject)
}
}
// 改变了promise的状态后下一步就需要固定promise的状态
// 其实很简单,只需要判断当前状态是不是pending即可,若不是直接返回
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class myPromise {
#state = PENDING;
constructor(executor) {
const resolve = (data) => {
if (this.#state != PENDING) return;
this.#state = FULFILLED;
};
const reject = (err) => {
if (this.#state != PENDING) return;
this.#state = REJECTED;
};
executor(resolve, reject)
}
}
// 仔细看这里的代码是不是进行一个简单的封装操作,我们可以单独写一个改变状态的方法
// 然后分别在里面进行调用
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class myPromise {
#state = PENDING;
constructor(executor) {
const resolve = (data) => {
this.#changeState(FULFILLED, data)
};
const reject = (err) => {
this.#changeState(REJECTED, err)
};
executor(resolve, reject)
}
// 这里同样是只能在类里面使用的方法,因为外部并不需要获取到此方法
#changeState(state, result) {
if (this.#state != PENDING) return;
this.#state = state;
}
}
// 状态改变后还可以获取resolve或reject方法中传递的参数 例如resolve(123)
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class myPromise {
#state = PENDING;
#result = undefined; // 初始值可以设为undefined
constructor(executor) {
const resolve = (data) => {
this.#changeState(FULFILLED, data)
};
const reject = (err) => {
this.#changeState(REJECTED, err)
};
executor(resolve, reject)
}
#changeState(state, result) {
if (this.#state != PENDING) return;
this.#state = state;
this.#result = result; //在这里先保存一下传递过来的结果
console.log(this.#state,this.#result); // 分别进行测试打印
}
}
// demo1
const p = new myPromise((resolve,reject)=>{
resolve(123); // fulfilled 123
})
//demo2
const p = new myPromise((resolve,reject)=>{
reject('失败了'); // rejected 失败了
resolve('成功了'); // 不再执行
})
2、还有一种情况就是在原生promise执行的过程中如果出现报错,那么最后的状态也将会是rejected
// 先看一下原生promise
const p = new Promise((resolve,reject)=>{
throw '报错了'
})
console.log(p);
//[[Prototype]]: Promise
//[[PromiseState]]: "rejected"
//[[PromiseResult]]: "报错了"
// 思路就是我们只需要对这个错误进行捕获,如果捕获到了就直接将状态变为rejected即可
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class myPromise {
#state = PENDING;
#result = undefined;
constructor(executor) {
const resolve = (data) => {
this.#changeState(FULFILLED, data)
};
const reject = (err) => {
this.#changeState(REJECTED, err)
};
// 对这个方法的调用使用try catch捕获
try {
executor(resolve, reject)
} catch (err) {
this.#changeState(REJECTED,err)
}
}
#changeState(state, result) {
if (this.#state != PENDING) return;
this.#state = state;
this.#result = result
console.log(this.#state, this.#result) // rejected 我报错了
}
}
const p = new myPromise((resolve,reject)=>{
throw '我报错了'
})
但是这个错误如果是一个异步中的错误将无法对其进行捕获,在原生promise中也是无法进行捕获,这里也是一个小的考点哦!例如:
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
throw 123
},1000)
})
console.log(p);
//[[Prototype]]: Promise
//[[PromiseState]]: "pending"
//[[PromiseResult]]: undefined
3、then方法
首先then方法接受两个回调函数分别是成功和失败后的回调,并返回一个promise对象 ,于是可以写出下面的代码:
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class myPromise {
#state = PENDING;
#result = undefined;
constructor(executor) {
const resolve = (data) => {
this.#changeState(FULFILLED, data)
};
const reject = (err) => {
this.#changeState(REJECTED, err)
};
try {
executor(resolve, reject)
} catch (err) {
this.#changeState(REJECTED, err)
}
}
#changeState(state, result) {
if (this.#state != PENDING) return;
this.#state = state;
this.#result = result
console.log(this.#state, this.#result)
}
then(onFulFilled,onRejected){
return new myPromise((resolve,reject)=>{
})
}
}
那么现在就面临了两个比较大的问题:
1、onFulfilled和onRejected两个函数什么时候调用
2、如何获取返回promise的状态(成功还是失败)
第一个问题稍微简单一点,只需要根据当前promise的状态来调用即可
then(onFulfilled,onRejected){
return new myPromise((resolve,reject) => {
// 如果成功状态就调用成功的回调函数并把成功后的信息传入,失败亦是
if(this.#state == FULFILLED){
onFulfilled(this.#result)
}else if(this.#state == REJECTED){
onRejected(this.#result)
}
})
}
// demo
const p = new myPromise((resolve,reject)=>{
resolve(123)
})
p.then((res)=>{
console.log(res) // 123
},(err)=>{
console.log(err)
})
那如果resolve(123)没有很快完成呢
const p = new myPromise((resove,reject)=>{
setTimeout(()=>{
resolve(123)
},1000)
})
这个时候then方法其实是不会获取到成功后传递的数据的,因为在resolve执行之前then方法已经被执行过了,那这个问题应该如何解决呢?
我们的目的其实就是在resolve方法执行后立即执行then方法中的回调函数,现在函数并没有立即执行是因为在then方法中并不知道当前的promise处于什么状态(因为resolve方法被延迟执行了,所以then方法会在resolve方法之前被执行,resolve方法执行完之后then方法已经不会被执行了)
找到问题所在后可以得出以下思路:
我们可以在then方法执行时先对两个传递进来的回调函数进行保存,当状态改变之后对保存的回调函数以此进行调用
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class myPromise {
#state = PENDING;
#result = undefined;
// 用于保存then方法中的回调函数等信息,then方法可能会被调用多次
// 所以回调函数也可能会是多个所以这里声明一个数组
#handlers = [];
constructor(executor) {
const resolve = (data) => {
this.#changeState(FULFILLED, data)
};
const reject = (err) => {
this.#changeState(REJECTED, err)
};
try {
executor(resolve, reject)
} catch (err) {
this.#changeState(REJECTED, err)
}
}
// 因为这个方法是改变promise状态的,所以then方法中的逻辑也应该在这里执行一次
#changeState(state, result) {
if (this.#state != PENDING) return;
this.#state = state;
this.#result = result
console.log(this.#state, this.#result)
this.#run()
}
// 直接封装一个方法来解决延迟改变状态的问题
#run(){
// 延迟修改状态后then方法执行时此时状态为pending,不需要处理
if(this.#state == PENDING) return;
// 依次对存储的回调函数进行执行
while(this.#handlers.length){
const {onFulfilled,onReject} = this.#handlers.shift();
if (this.#state == FULFILLED) {
// 判断传递的是否是一个函数
if (typeof onFulfilled == 'function') {
onFulfilled(this.#result)
}
} else if (this.#state == REJECTED) {
if (typeof onRejected == 'function') {
onRejected(this.#result)
}
}
}
}
then(onFulfilled, onRejected) {
return new myPromise((resolve, reject) => {
this.#handlers.push({
onFulfilled,
onRejected,
// 后面第二个问题会讲到
resolve,
reject
})
// 这里也需要执行,如果状态没有状态被延迟修改则直接执行对应的回调函数
this.#run();
})
}
}
// demo
const p = new myPromise((resolve, reject) => {
setTimeout(() => {
resolve(123)
}, 1000)
})
p.then((res) => {
console.log('res1', res)
}, (err) => {
console.log('err1', err)
})
p.then((res) => {
console.log('res2', res)
}, (err) => {
console.log('err2', err)
})
p.then((res) => {
console.log('res3', res)
}, (err) => {
console.log('err3', err)
})
// res1 123
// res2 123
// res3 123
到此为止第一个问题就已经被解决了,下面看第二个问题,返回的promise状态该如何确定呢?
这个问题也分为三种情况:
1、传递对应的回调函数不是函数;
p.then(null,(err)=>{})
// 原生promise中
const p = new Promise((resolve,reject)=>{
resolve(123);
})
p.then(null,()=>{}).then(res=>{
console.log("res",res); // res 123
})
这种情况在原生promise中会进行穿透,即找到成功或失败对应的回调函数才会停止向后传递信息。
#run(){
if(this.#state == PENDING) return;
while(this.#handlers.length){
const {onFulfilled,onReject} = this.#handlers.shift();
if (this.#state == FULFILLED) {
if (typeof onFulfilled == 'function') {
onFulfilled(this.#result)
}else{ // 传递的回调函数不是函数时
// 若不是函数传递的还是第一个promise的结果
// 会跟当前的promise状态保持一致
resolve(this.#result);
}
} else if (this.#state == REJECTED) {
if (typeof onRejected == 'function') {
onRejected(this.#result)
}else{
reject(this.#result)
}
}
}
}
// demo
const p = new myPromise((resolve, reject) => {
resolve(123)
})
p.then(null, (err) => {
console.log('err', err)
}).then(res=>{
console.log("res",res)
})
至此就已经解决了这种情况。
2、传递的回调函数是函数,是函数的话就需要看这个函数跟返回的promise的状态有什么关系,即函数执行没有报错的话就是resolve,有报错就是rejected,所以要给函数的运行加上try catch
#run(){
if(this.#state == PENDING) return;
while(this.#handlers.length){
const {onFulfilled,onReject} = this.#handlers.shift();
if (this.#state == FULFILLED) {
if (typeof onFulfilled == 'function') {
try{
const data = onFulfilled(this.#result)
resolve(data)
}catch(err){
reject(data)
}
}else{ // 传递的回调函数不是函数时
// 若不是函数,则传递的还是第一个promise的结果,
// 会跟当前的promise状态保持一致
resolve(this.#result);
}
} else if (this.#state == REJECTED) {
if (typeof onRejected == 'function') {
try{
const data = onRejected(this.#result)
resolve(data)
}catch(err){
reject(err)
}
}else{
reject(this.#result)
}
}
}
}
// 优化一下代码
// 添加一个新的方法
#runOne(callback,resolve,reject){
if(typeof callback != 'function'){
const sattled = this.#state == FULFILLED ? resolve :reject;
sattled(this.#result);
return;
}
try{
const data = callback(this.#result);
resolve(data);
}catch(err){
reject(err)
}
}
#run(){
if(this.#state == PENDING) return;
while(this.#handlers.length){
const {onFulfilled,onReject} = this.#handlers.shift();
if (this.#state == FULFILLED) {
this.#runOne(onFulfilled,resolve,reject);
} else if (this.#state == REJECTED) {
this.#runOne(onRejected,resolve,reject);
}
}
}
3、函数返回的结果是一个promise对象
// 判断一个对象是否为promise对象
isPromise(obj){
return obj && typeof obj == 'object' && typeof obj.then == 'function';
}
#runOne(callback,resolve,reject){
if(typeof callback != 'function'){
const sattled = this.#state == FULFILLED ? resolve :reject;
sattled(this.#result);
return;
}
try{
const data = callback(this.#result);
if(this.isPromise(data)){
// 如果then方法返回的是一个promise对象
// 就把当前then方法返回的promise中的resolve和reject传进去
// 来确认当前then方法返回的promise的状态
data.then(resolve,reject)
}else{
resolve(data)
}
}catch(err){
reject(err)
}
}
最后一个问题then里面的函数是运行在微队列中的:
#runMicroTask(func){
// 使用setTimeout模拟
setTimeout(func,0)
}
#runOne(callback,resolve,reject){
this.#runMicroTask(()=>{
if(typeof callback != 'function'){
const sattled = this.#state == FULFILLED ? resolve :reject;
sattled(this.#result);
return;
}
try{
const data = callback(this.#result);
if(this.isPromise(data)){
data.then(resolve,reject)
}else{
resolve(data)
}
}catch(err){
reject(err)
}
});
}
五、手写深拷贝
浅拷贝:如果是基本数据类型的值则直接拷贝基本数据类型的值,如果是引用类型的值则拷贝的是一个堆内存对象中的引用地址,即一个值的变化会影响另一个值的变化。Object.assign()、slice()、concat()等。
深拷贝:一个值的变化不会影响另一个值的变化。JSON.stringify()、jquery.extend()、lodash._cloneDeep()、手写递归,下面主要演示手写递归的方法。
要实现深拷贝必不可少的是需要判断数据的类型,所以可以单独抽离出一个方法专门用来判断数据的类型:
function checkType(data) {
return Object.prototype.toString.call(data).slice(8, -1);
}
之所以要进行截取是因为Object.prototype.toString.call()方法返回的是[object Object]格式,我们只需要最后的类型即可。下面来实现深拷贝函数。
function deepClone(target) {
let type = checkType(target);
let result;
if (type == 'Object') {
result = {};
} else if (type == 'Array') {
result = [];
} else {
return target;
}
for (let key in target) {
let value = target[key];
let valueType = checkType(value);
// 如果属性值依然为引用类型则再次执行该方法
if (valueType == 'Object' || valueType == 'Array') {
result[key] = deepClone(value);
} else {
// 直到为基本类型为止
result[key] = value;
}
}
return result;
}
首先要对传入的数据进行类型的判断,如果是基本数据类型不需要进行深拷贝直接返回,如果是引用数据类型则针对不同的数据类型作初始化操作。紧接着对传递的数据进行遍历,属性值可能也会是引用类型所以需要进行递归,直至全部为基本数据类型的时候再进行整体返回。
let obj1 = {
name: "小明",
age: 20,
hobby: ['sing','dance']
}
let obj2 = deepClone(obj1)
obj2.hobby[0] = 'sleep'
console.log(obj1, obj2)
// { name: '小明', age: 20, hobby: [ 'sing', 'dance' ] }
// { name: '小明', age: 20, hobby: [ 'sleep', 'dance' ] }
六、手写防抖函数
函数防抖是指在一定时间内,多次触发事件只执行最后一次事件处理函数。
例如:搜索框输入事件、鼠标移入移出事件
const box = document.querySelector('#box');
box.onmousemove = debounce(() => {
console.log('mousemove');
}, 1000)
function debounce(fn,delay){
let timer = null;
return function(){
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
fn()
},delay)
}
}
手写节流函数
连续发生的事件在 n 秒内只执行一次,限制函数在一定时间内的执行次数.
function throttle(fun, delay) {
let timeout;
return function () {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
fun();
}, delay);
}
};
}
持续更新ing。。。。。。
时间就像海绵。