浅拷贝
深拷贝
-
ES5 常用克隆对象方法
- 数组和普通对象的克隆
// 只能实现数组或者普通对象的克隆,不能实现包装对象Number,String,Boolean,以及正则对象RegExp和Date对象的克隆, function deepClone(obj){ // 判断是数组还是对象 var newObj= obj instanceof Array ? []:{}; // for循环遍历对象的属性 for(var item in obj){ // 如果对象内部某个属性是对象则递归 var temple= typeof obj[item] == 'object' ? deepClone(obj[item]):obj[item]; newObj[item] = temple; } return newObj; }
- valueof()函数实现原始类和 包装对象的克隆
所有对象都有valueOf方法,valueOf方法对于:如果存在任意原始值,它就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值。数组、函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的valueOf()方法只是简单返回这个对象本身。
function baseClone(base){ return base.valueOf(); } //Number var num=new Number(1); var newNum=baseClone(num); //newNum->1 //String var str=new String('hello'); var newStr=baseClone(str); // newStr->"hello" //Boolean var bol=new Boolean(true); var newBol=baseClone(bol); //newBol-> true
-
Date类型克隆
Date.prototype.clone=function(){ return new Date(this.valueOf()); // 日期类定义的valueOf()方法会返回它的一个内部表示:1970年1月1日以来的毫秒数 } var date=new Date('2010'); var newDate=date.clone(); // newDate-> Fri Jan 01 2010 08:00:00 GMT+0800
-
RegExp对象克隆
RegExp.prototype.clone = function() { var pattern = this.valueOf(); var flags = ''; flags += pattern.global ? 'g' : ''; flags += pattern.ignoreCase ? 'i' : ''; flags += pattern.multiline ? 'm' : ''; return new RegExp(pattern.source, flags); }; var reg=new RegExp('/111/'); var newReg=reg.clone(); //newReg-> /\/111\//
Object.assign()
通过复制一个或多个对象来创建一个新的对象。
Object.create()
使用指定的原型对象和属性创建一个新对象。
Object.defineProperty()和Object.defineProperties()
- 数组和普通对象的克隆
Object.defineProperty()给对象添加一个属性并指定该属性的配置。 Object.defineProperties()给对象添加多个属性并分别指定它们的配置。
-
在ES5中可以通过Object.defineProperty来实现已有属性的监听
缺点:
1. 无法监听数组的变化: 数组的这些方法是无法触发set的:push, pop, shift, unshift,splice, sort, reverse.,vue中能监听是因为对这些方法进行了重写 2. 只能监听属性,而不是监听对象本身,需要对对象的每个属性进行遍历。对于原本不在对象中的属性难以监听。
// Object.defineProperty(obj, prop, descriptor)
var obj={};
Object.defineProperty(obj,'data',{
get:function(){
return data;
},
set:function(newValue){
data=newValue;
console.log('set :',newValue);
//需要触发的渲染函数写在这...
}
})
// 当我们给obj的data赋值的时候,就会触发set 的方法
obj.data=5;//set: 5
// 如果要一下子定义多个变量的getter和setter,你可以使用Object.defineProperties(obj,props)
// obj:要在其上定义属性的对象。props:要定义其可枚举属性或修改的属性描述符的对象。
var obj = {};
Object.defineProperties(obj, {
a: {
configurable: true, //表示该属性描述符可以被改变(默认为false)
get: function() {
console.log('get: ',a)
return a
},
set: function(newValue) {
a = newValue;
console.log('set: ',a)
}
},
b: {
configurable: true,
get: function() {
console.log('get: ',b)
return b;
},
set: function(newValue) {
b = newValue;
console.log('set: ',b)
}
}
})
// 脏值检测原理就是比较新值和旧值, 当值真的发生改变时再去更改DOM,目前Angular使用脏值检测
// 缺点是如果不注意,每次脏值检测会检测大量的数据, 而很多数据是没有检测的必要的,容易影响性能。
let data = { price: 5, quantity: 2 }
// 监听属性变化的函数
let target = null
// 依赖收集
class Dep {
constructor () {
this.subscribers = []
}
depend () {
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target)
}
}
notify () {
this.subscribers.forEach(sub => sub())
}
}
// 遍历对象:返回一个包含所有给定对象自身可枚举属性名称的数组。
Object.keys(data).forEach(key => {
let internalValue = data[key]
const dep = new Dep()
Object.defineProperty(data, key, {
// 对象方法的简写形式
get() {
dep.depend()
return internalValue
},
set(newVal) {
internalValue = newVal
dep.notify()
}
})
})
function watcher(myFun) {
target = myFun
target()
target = null
}
watcher(() => {
data.total = data.price * data.quantity
})
console.log("total = " + data.total)
data.price = 20
console.log("total = " + data.total)
data.quantity = 10
console.log("total = " + data.total)
// vue中的响应式原理就是如此,遍历所有的响应式的数据,进行依赖收集,响应数据的改变。
- 在ES6中可以通过Proxy来实现
优点:
1. 可以监听数组变化
2. 监听的是对象本身
3. 有13种拦截方法
4. Proxy允许我们创建一个对象的虚拟代理(替代对象),并为我们提供了在访问或修改原始对象时,可以进行拦截的处理方法(handler),如set()、get()和deleteProperty()等等。
// 我们可以使用以下方法在data对象上建立一个代理,而不是遍历每个属性来添加getter/setter。
// data 是我们准备要创建代理的源对象
const observedData = new Proxy(data, {
get() {
// 访问源对象属性时调用
},
set() {
// 修改源对象属性时调用
},
deleteProperty() {
// 删除源对象属性时调用
}
});
// 使用Proxy实现响应式
let data = { price: 5, quantity: 2 }
let deps = new Map(); // 创建一个Map对象
// Dep class并不需要改动。单纯使用Proxy替换Object.defineProperty
class Dep {
constructor () {
this.subscribers = []
}
depend () {
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target)
}
}
notify () {
this.subscribers.forEach(sub => sub())
}
}
Object.keys(data).forEach(key => {
// 为每个属性都设置一个依赖实例并放入deps 中
deps.set(key, new Dep());
});
let data_without_proxy = data; // 保存源对象
data = new Proxy(data_without_proxy, {
// 重写数据以在中间创建一个代理
get(obj, key) {
deps.get(key).depend(); // <-- 依旧为存储target
return obj[key]; // 返回原始数据
},
set(obj, key, newVal) {
obj[key] = newVal; // 将原始数据设置为新值
deps.get(key).notify(); // <-- 依旧为重新运行已存储的target
return true;
}
});
function watcher(myFun) {
target = myFun
target()
target = null
}
watcher(() => {
total = data.price * data.quantity;
});
console.log("total = " + total);
data.price = 20;
console.log("total = " + total);
data.quantity = 10;
console.log("total = " + total);
- 使用Proxy在未声明情况下添加新的响应式属性
let data = { price: 5, quantity: 2 };
let target = null;
class Dep {
constructor() {
this.subscribers = [];
}
depend() {
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target);
}
}
notify() {
this.subscribers.forEach(sub => sub());
}
}
// 前边的代码都没变
let deps = new Map(); // 创建一个Map对象
Object.keys(data).forEach(key => {
// 为每个属性都设置一个依赖实例 并放入 deps 中
deps.set(key, new Dep());
});
let data_without_proxy = data; // 保存源对象
data = new Proxy(data_without_proxy, {
// 重写数据以在中间创建一个代理
get(obj, key) {
deps.get(key).depend(); // <-- 依旧为存储target
return obj[key]; // 返回原始数据
},
set(obj, key, newVal) {
obj[key] = newVal; // 将原始数据设置为新值
deps.get(key).notify(); // <-- 依旧为重新运行已存储的targets
return true;
}
});
// 用来监听具有响应性属性的代码
function watcher(myFunc) {
target = myFunc;
target();
target = null;
}
let total = 0
watcher(() => {
total = data.price * data.quantity;
});
console.log("total = " + total);
data.price = 20;
console.log("total = " + total);
data.quantity = 10;
console.log("total = " + total);
// 为dep添加一个新属性,存储依赖到Map中
deps.set('discount', new Dep())
// 添加不存在data中的属性
data['discount'] = 5;
let salePrice = 0;
// 添加监听,会增加target 对其进行监听,其中包括我们新添加的属性
watcher(() => {
salePrice = data.price - data.discount;
});
console.log("salePrice = " + salePrice); // 15
data.discount = 7.5 // 此时就会调用我们的监听函数,达到响应式的目的
console.log("salePrice = " + salePrice); // 12.5
Object.getOwnPropertyDescriptor()
返回对象指定的属性配置。
Object.getPrototypeOf()
返回指定对象的原型对象。 获取一个对象的原型,在chrome中可以通过_proto_的形式,或者在ES6中可以通过Object.getPrototypeOf的形式。
// 那么Function.proto是什么么?也就是说Function由什么对象继承而来,我们来做如下判别。
Function.__proto__==Object.prototype //false
Function.__proto__==Function.prototype//true
// 我们发现Function的原型也是Function。
Object.getOwnPropertyNames()
返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。
Object.keys()
Object.keys()返回一个包含所有给定对象自身可枚举属性名称的数组。Object.values() 返回给定对象自身可枚举值的数组。
Object.entries()
返回给定对象自身可枚举属性的 [key, value] 数组。
Object.observe()
Object.observe() 方法用于异步地监视一个对象的修改。当对象属性被修改时,方法的回调函数会提供一个有序的修改流。然而,这个接口已经被废弃并从各浏览器中移除。你可以使用更通用的 Proxy 对象替代。
// 打印changes
var obj = {
foo: 0,
bar: 1
};
Object.observe(obj, function(changes) {
console.log(changes);
});
obj.baz = 2;
// [{name: 'baz', object: <obj>, type: 'add'}]
obj.foo = 'hello';
// [{name: 'foo', object: <obj>, type: 'update', oldValue: 0}]
delete obj.baz;
// [{name: 'baz', object: <obj>, type: 'delete', oldValue: 2}]
// 数据绑定
// 一个数据模型
var user = {
id: 0,
name: 'Brendan Eich',
title: 'Mr.'
};
// 创建用户的greeting
function updateGreeting() {
user.greeting = 'Hello, ' + user.title + ' ' + user.name + '!';
}
updateGreeting();
Object.observe(user, function(changes) {
changes.forEach(function(change) {
// 当name或title属性改变时, 更新greeting
if (change.name === 'name' || change.name === 'title') {
updateGreeting();
}
});
});
Object.prototype.toString.call()
用于准确判断对象的类型的方法
同样是检测对象obj调用toString方法(关于toString()方法的用法的可以参考toString的详解),obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString为Object的原型方法,而Array 、Function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(Function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…..),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object上原型toString方法。
请阅读MDN