手写代码
大约 4 分钟
手写代码
深度比较,模拟 lodash.isEqual
// 判断是否是对象或数组
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
function isEqual(obj1, obj2) {
if (!isObject(obj1) || !isObject(obj2)) {
// 值类型(注意,参数 equal 的一般不会是函数)
return obj1 === obj2
}
if (obj1 === obj2) {
return true
}
// 两个都是对象或数组,而且不相等
// 1. 先取出 obj1 和 obj2 的 keys,比较个数
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false
}
// 2. 以 obj1 为基数,和 obj2 一次递归比较
for (let key in obj1) {
// 比较当前 key 的 val —— 递归
const res = isEqual(obj1[key], obj2[key])
if (!res) {
return false
}
}
// 全相等
return true
}
call、apply 及 bind 函数
call
(接受的是一个参数列表)Function.prototype.myCall = function(context) { if (typeof this !== 'function') { throw new TypeError('Error); } context = context || window; context.fn = this; const args = [...argumets].slice(1); const result = context.fn(...args); delete context.fn; return result; }
context
为可选参数,如果不传的话默认上下文为window
- 给
context
创建一个fn
属性,并将值设置为需要调用的函数 call
可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来- 然后调用函数并将对像上的函数删除
apply
(接受的是一个包含多个参数的数组)Function.prototype.myApply = function (context) { if (typeof this !== 'function') { throw new TypeError('Error') } context = context || window context.fn = this let result // 处理参数和 call 有区别 if (arguments[1]) { result = context.fn(...arguments[1]) } else { result = context.fn() } delete context.fn return result }
bind
(返回对应函数)Function.prototype.myBind = function (context) { if (typeof this !== 'function') { throw new TypeError('Error') } const _this = this const args = [...arguments].slice(1) // 返回一个函数 return function F() { // 因为返回了一个函数,我们可以 new F(),所以需要判断 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, args.concat(...arguments)) } }
bind
返回了一个函数,对于函数来说有两种方式调用:直接调用
使用
apply
的方式实现,但对于参数需要注意一下情况:因为bind
可以实现类似这样的代码f.bind(obj, 1)(2)
,所以需要将两边的参数拼接起来,于是就有了这样的实现args.concat(...arguments)
通过
new
的方式对于
new
的情况来说,不会被任何方式改变this
,所以对于这种情况需要忽略传入的this
字符串 trim 方法,保证浏览器兼容性
String.prototype.trim = function () {
return this.replace(/^\s+/, '').replace(/\s+$/, '')
}
获取当前页面 url 参数
// 传动方式
function query(name) {
const search = location.search.substr(1)
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')
const res = search.match(reg)
if (res === null) {
return null
}
return res[2]
}
// URLSearchParams
function query(name) {
const search = location.search
const p = new URLSearchParams(seach)
return p.get(name)
}
手写数组 flatern, 考虑多层级
function flat(arr) {
// 验证arr中,还有没有深层数组 [1,2,[3,4]]
const isDeep = arr.some(item => item instanceof Array)
if (!isDeep) {
return arr
}
const res = Array.prototype.concat.apply([], arr)
return flat(res) // 递归
}
浅拷贝与深拷贝
和原数据是否指向同一对象 | 第一层数据为基本数据类型,改变是否会使原数据改变 | 原数据中包含子对象,改变是否会使原数据改变 | |
---|---|---|---|
赋值 | 是 | 会 | 会 |
浅拷贝 | 否 | 不会 | 会 |
深拷贝 | 否 | 不会 | 不会 |
浅拷贝:只拷贝第一层的原始类型值,和第一层的引用类型地址
- 对象的
Object.assign
:用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。 - 数组的
Array.prototype.slice()
和Array.prototype.concat()
- ES6 扩展运算符
...
- 对象的
深拷贝:拷贝所有的属性值,以及属性地址指向的值的内存空间
JSON.parse(JSON.stringify(object))
局限性:会忽略
undefined
、会忽略symbol
、不能序列化函数(会忽略函数)、不能解决循环引用的对象(会报错)MessageChannel
MessageChannel
创建了一个通信的管道,这个管道有两个端口,每个端口都可以通过postMessage
发送数据,而一个端口只要绑定了onmessage
回调方法,就可以接收从另一个端口传过来的数据。局限性:不能包含函数,否则会报错
// 注:该方法是异步,可以处理 undefined 和循环引用对象 function structuralClone(obj) { return new Promise(resolve => { const { port1, port2 } = new MessageChannel() port2.onmessage = ev => resolve(ev.data) port1.postMessage(obj) }) }
简易版深拷贝和
lodash
的深拷贝函数实现深拷贝需要考虑很多边界情况,比如原型链如何处理、DOM 如何处理等等。可以使用
lodash
的深拷贝函数简易版深拷贝
function deepClone(obj) { function isObject(o) { return (typeof o === 'object' || typeof o === 'function') && o !== null } if (!isObject(obj)) { throw new Error('非对象') } let isArray = Array.isArray(obj) let newObj = isArray ? [...obj] : { ...obj } // Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组 Reflect.ownKeys(newObj).forEach(key => { newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return newObj }
注:深拷贝还需要考虑到 引用丢失 和 递归爆栈 的问题
引用丢失
var b = {} var a = { a1: b, a2: b } a.a1 === a.a2 // true var c = clone(a) // 深拷贝 c.a1 === c.a2 // false
递归爆栈:当数据的层次很深是就会栈溢出
参考:深拷贝的终极探索