标题对象的深浅拷贝
在进行实例讲解之前,我们先下个给深浅拷贝下个定义,什么是深拷贝?什么是浅拷贝?
个人理解:
- 浅拷贝:针对指针的引用
- 深拷贝: 对值的引用
浅拷贝
先看?:
var obj = {
a: 1,
b: 2,
};
var newObj = obj
obj.a = 3
console.log(obj.a) // 3
console.log(newObj.a) // 3
// 浅拷贝实现的方法
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
深拷贝
var obj = {
a: 1,
b: 2,
};
var newObj = {};
Object.keys(obj).map(item => {
newObj[item] = obj[item];
});
obj.a = 666
console.log(obj.a) // 666
console.log(newObj.a) // 1
比较?1 和 例子2 的不同,这里将 1 (针对地址的引用) 称之为浅拷贝,2 (针对属性的拷贝) 称之为深拷贝,
区别也是显而易见的
案列:实现对象深拷贝的
var obj = {
a: 1,
b: 2,
c: { sex: "man", options: { age: 22 } }
};
var newObj = {};
function objDeep(newObj, obj) {
Object.keys(obj).map(item => {
if (obj[item] && !(obj[item] instanceof Object)) {
newObj[item] = obj[item];
} else {
newObj[item] = {};
arguments.callee(newObj[item], obj[item]);
}
});
}
objDeep(newObj, obj);
newObj.a = 999
console.log(obj.a) // 1
console.log(newObj.a) // 999
// 但是以上代码会存在一些问题,如果对象的层级太深,会出现递归爆栈
// 更加优雅的实现方式
var a = {
a1: 1,
a2: {
b1: 1,
b2: {
c1: 3,
c2: {
d1: 1
}
}
}
}
function cloneLoop(x) {
const root = {};
// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x, // {a1: 1, a2: {b1: 1}}
}
];
while(loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
cloneLoop(a)
Object.assign()
既然提到对象的浅拷贝,就不得不提到 Object.assign()
MDN中的解释为:Object.assign() 方法用于将 所有可枚举属性的值 从一个或多个源对象复制到目标对象。它将返回目标对象。
举例:
Object.assign(target, source)
var obj = { a: 1, b: 2 };
var copy = Object.assign({a: 3}, obj);
console.log(copy); // { a: 1, b: 2 }
// 手动实现封装一个 Object.assign() 方法,以下简称为assign2()
// step1: 判断是否存在Object.assign2方法
if (typeof Object.assign2 !== 'function') {
// step2: 自定义一个
Object.defineProperty(Object, "assign2", {
value: function (target) {
'use strict';
// 判断目标对象是否为空
if (target == null) { // Attention 2
throw new TypeError('Cannot convert undefined or null to object');
}
// 创建一个对象
var to = Object(target);
for (var i = 1, len = arguments.length; i < len; i++) {
var nextSource = arguments[i]; // 获取source对象,去掉target对象
if (nextSource != null) { // Attention 2
// Attention 4
for (var nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
// 测试一下
// 测试用例
let a = {
name: "advanced",
age: 18
}
let b = {
name: "muyiy",
book: {
title: "You Don't Know JS",
price: "45"
}
}
let c = Object.assign2(a, b);
console.log(c);
这里注意:源对象中的属性会代替目标对象的同名属性,这也就导致目标对象中的 {a: 3} 最终被替换为 {a: 1} 的结果
既然谈到了是可枚举属性,那我们再来看看不可枚举属性
对象的 数据属性 和 访问器属性 请自行查看 高层三, pdf 下载地址
// 通过 Object.create() 创建的对象,默认都是不可枚举的,需要手动设置
var obj = Object.create({foo: 1}, { // foo 是个继承属性。
a: {
name: 'lilie' // a 是个不可枚举属性。
},
b: {
name: 'lili',
enumerable: true // b 是个自身可枚举属性。
}
});
var copy = Object.assign({}, obj);
console.log(copy); // { b: 'lili' }
为啥Object.assign() 是浅拷贝,我们再来看一个?:
var obj = { a: 1, b: [1,3],c: {name: 'name', age: 30} };
var copy = Object.assign({a: 3}, obj);
obj.a = 999
console.log(obj.a) // 999
console.log(copy.a); // 1
写到这里,根据控制台打印结果,我反问自己,这不就是深拷贝么,事实上,你可以简单粗暴的这么理解,因为MDN解释很清楚了, Object.assign() 是对值的引用
- 到次为此,我们已经知道了两种关于对象深拷贝的方法
我们不妨再来一个骚操作:
var obj = { a: 1, b: [1,3],c: {name: 'name', age: 30} };
var copy = JSON.parse(JSON.stringify(obj)) // 先转化为字符串,在转化为json对象,相当于完完全全拷贝了一个副本
obj.a = 999
console.log(obj.a) // 999
console.log(copy.a); // 1
到这里,应该基本上差不多了
但是笔者我不忍心,Object.assign() 感觉很像对象的去重复,各位看官有木有,既然如此,我们顺便把 数组的去重 和 扁平化处理 的面试常遇到的代码顺便分享一下
一下关于 es6 的相关知识,如果不太了解,请移步至 es6 教程
数组的去重
let arr = [1, 5, 46, 79, 46, 78, 941, 1111, 1, 1, 1, 1, 1, 1, 1]
let arr0 = [...new Set(arr)] // 通过 es6 的 set 结合 数组的扩展语法
console.log(arr0)
let arr1 = Array.from(new Set(arr)) // 通过 es6 的 set 结合 数组的Array.from
console.log(arr1)
[1,3,4,5,1,2,3,3,4,8,90,3,0,5,4,0].filter(function(elem,index,Array){
return index === Array.indexOf(elem);
})
console.log(r)
let arr2 = [] // 土鳖的循环
for (let i = 0; i < arr.length; i++) {
if (arr2.indexOf(arr[i]) === -1) arr2.push(arr[i])
}
console.log(arr2)
//自定义添加方法去重复方法 可以是各种类型
var arr3= [0, 1, '1', true, 5, true, false, undefined, undefined, null, null];
if (!Array.prototype.unique) {
Array.prototype.unique = function () {
var hash = {}, result = [], type = '', item;
for (var i = 0; i < this.length; i++) {
item = this[i];
type = Object.prototype.toString.call(item);// 专门用来判断字符串的类型的
if ( !hash[item + type] ) {
hash[item + type] = true;
result.push(item);
}
}
return result;
};
}
console.log(arr3.unique());
//对象的方法来找出先次数最多的数字
var adv={
init:function(){
var arr=[1,2,5,2,5,5,4,1,1,1,2,2,2];
function findNum(arr){
for(var i=0,hash=[];i<arr.length;i++){
hash[arr[i]]=0;
for(var r=0;r<arr.length;r++){
if(arr[i]==arr[r]){
hash[arr[i]]++;
}
}
}
get(hash);
//[4, 5, 1, 3]
// 1 2 4 5
}
function get(hash){
var arr=[];
for(var key in hash){
arr.push(hash[key]);
}
arr.sort(function(a,b){return b-a});
for(var key in hash){
if(hash[key]==arr[0])
console.log(key);
}
}
findNum(arr);
}
}
扁平化处理
/** 数组相关的常用方法 */
const aboutArrayFun = {
/** 扁平化处理 */
// 递归
flattenOne: arr => {
var res = [];
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flattenOne(arr[i]));
} else {
res.push(arr[i]);
}
}
return res;
},
// reduce
flattenTwo: arr => {
return arr.reduce(function(prev, item) {
return prev.concat(Array.isArray(item) ? flattenTwo(item) : item);
}, []);
},
// 扩展运算符
flattenThree: arr => {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
},
}
关于对象的深浅拷贝就到这里,以上是个人的理解,其实并不难,只要抓住了重点,
推荐一篇博文,关于深拷贝讲的最好的,没有之一 深拷贝的终极探索(99%的人都不知道)
其它前端性能优化:
- 图片优化——质量与性能的博弈
- 浏览器缓存机制介绍与缓存策略剖析
- webpack 性能调优与 Gzip 原理
- 本地存储——从 Cookie 到 Web Storage、IndexDB
- CDN 的缓存与回源机制解析
- 服务端渲染的探索与实践
- 解锁浏览器背后的运行机制
- DOM 优化原理与基本实践
- Event Loop 与异步更新策略
- 回流(Reflow)与重绘(Repaint)
- Lazy-Load
- 事件的节流(throttle)与防抖(debounce
- 前端学习资料下载
- 技术体系分类
前端技术架构体系(没有链接的后续跟进):
- 调用堆栈
- 作用域闭包
- this全面解析
- 深浅拷贝的原理
- 原型prototype
- 事件机制、
- Event Loop
- Promise机制、
- async / await原理、
- 防抖/节流原理
- 模块化详解、
- es6重难点、
- 浏览器熏染原理、
- webpack配置(原理)
- 前端监控、
- 跨域和安全、
- 性能优化(参见上面性能优化相关)
- VirtualDom原理、
- Diff算法、
- 数据的双向绑定
- [TCP协议(三次握手、四次挥手)](https://blog.csdn.net/woleigequshawanyier/article/details/85223642
- DNS域名解析
其它相关
欢迎各位看官的批评和指正,共同学习和成长
希望该文章对您有帮助,你的 支持和鼓励会是我持续的动力