小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
手撕面试常客之JavaScript的深浅拷贝(处理多种特殊值),看这一篇就够了。
-
- 需要深拷贝的对象,包括日期对象、正则对象、函数等一些特殊的对象。
let objClone = {
name: 'beijing',
age: 77,
open: true,
0: null,
1: undefined,
2: Symbol(),
arr: ['foo', 'bar'],
detail: {
address: '北京',
phone: '1234567890'
},
3: /^\d+$/,
4: new Date(),
5: new Error(),
6: 10n,
fn: function () {
console.log('哈哈');
},
body:document.body
};
-
- 这个深浅拷贝方法还用到了几个了工具类的方法我把它提取出来了。
- 2.1 检测数据类型的方法
const checkType = function checkType(obj) { if (obj == null) return obj + '' let reg = /^\[object ([a-zA-Z0-9]+)\]$/i, type = typeof obj, isObjorFunc = /^(object|function)$/i.test(type) type = isObjorFunc? reg.exec(Object.prototype.toString.call(obj))[1].toLowerCase() : type return type }- 2.2 检测是否是纯粹对象(直属类是Object或者由Object.create(null)创建的对象)该方法借鉴于JQuery源码中的工具方法,对其进行了稍加改动。
const isPlainObject = function isPlainObject(target){ let proto,//存放target的原型 Ctor //存放target的构造函数 if (!target || Object.prototype.toString.call(target) !== '[object Object]') return false proto = Object.getPrototypeOf(target) if (!proto) return true Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor let ObjectFunctionString = Function.prototype.toString.call(Object); //"function Object() { [native code] }" return typeof Ctor === "function" && Function.prototype.toString.call(Ctor) === ObjectFunctionString }- 2.3 检测是否是window对象,此方法借鉴于JQuery源码中的工具方法。
const isWindow = function isWindow(obj) { return obj != null && obj === obj.window //window对象 有个特殊的地方 window.window等于它自己,大家可以在浏览器试一下 }- 2.4 检测是否是类数组或者数组,此方法借鉴于JQuery源码中的工具方法。
const isArrayLike = function isArrayLike(obj){ if (obj == null) return false if(typeof obj !== 'object') return false let length = !!obj && "length" in obj && obj.length, type = checkType(obj) if (isWindow(obj)) return false //window对象上是有length属性的 记录iframe标签的个数 return type === 'array' || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj }- 2.5 // 迭代数组/类数组/对象,此方法借鉴于JQuery源码中的工具方法。
const each = function each(target, callback) { if(typeof callback !== 'function') callback = Function.prototype let i = 0,//记录循环的次数 len,//数组的长度 item,//target的每一项 keys,//如果target是对象 存放 对象key的列表 key;//如果target是对象 对象的某一个key if (isArrayLike(target)) { len = target.length for(; i < len; i++){ item = target[i] if (callback.call(target,item, i) === false) break //回调函数返回false 支持停止迭代 } } else { keys = Object.keys(target) if (typeof Symbol !== 'undefined') keys = keys.concat(Object.getOwnPropertySymbols(target)) for(; i<keys.length; i++){ key = keys[i] item = target[key] if ( callback.call(target, item, key) === false) break } } } -
- 工具方法准备完毕,接下来完成深浅拷贝方法,该方法有两个参数。
- 第一个参数deep为布尔值,true表示深拷贝,false表示浅拷贝,默认为false。
- 第二个参数为target,表示要克隆的目标。
const clone = function clone() { let target = arguments[0], deep = false, //深浅拷贝 type, // target 的类型 isArray, // target 是否是数组或者类数组 isObject, // target 是否是普通对象 ctor,// 存放 target的构造函数 result, //假如 target是数组或者对象,用来存放深拷贝后的值 treated = arguments[arguments.length-1] //为了防止死递归,存放已经处理过的对象 // 判断第一个参数是否为 boolean if (typeof target === 'boolean') { if (arguments.length === 1) return target; deep = target target = arguments[1] } // 为了防止死递归,循环引用的情况 if (!Array.isArray(treated) || !treated.treated) { treated = [] treated.treated = true } if(treated.includes(target)) return target treated.push(target) // 特殊值的拷贝 type = checkType(target) isArray = isArrayLike(target) isObject = isPlainObject(target) if(target == null) return target ctor = target.constructor // 特殊值拷贝 if (/^(regexp|date|error)$/i.test(type)) { if (type === 'error') target = target.message return new ctor(target) } if (/^(function|generatorfunction)$/i.test(type)) { return function proxy(...params){ return target.call(this, ...params) } } if (target instanceof HTMLElement) { return document.createElement(target.localName) } // 如果不是数组 也不是对象 返回 自己 if (!isArray && !isObject) return target // 数组和对象的拷贝 result = isArray ? [] : {} each(target, function(val, key){ if (deep) { // 深拷贝 // 深拷贝的时候把treated数组传进去,记录已经处理过的对象,防止循环引用,出现死递归的情况 result[key] = clone(deep,val, treated) return } //浅拷贝 result[key] = val }) return result } let newObj = clone(true,objClone) console.log(newObj); console.log(newObj.arr === objClone.arr); console.log(newObj.detail === objClone.detail); console.log(newObj[4] === objClone[4]); console.log(newObj[3] === objClone[3]); console.log(newObj[5] === objClone[5]); console.log(newObj.body === objClone.body); console.log(newObj.fn === objClone.fn);- 浏览器打印情况
结束语
JQuery虽然已经过时了,但是作为每个前端人都知道的一个类库,其源码必定是经过千锤百炼的,其中一些优秀的思想也是值得大家学习的,本文也借鉴了一些JQuery的方法,贴出来供大家学习。