Mixin模式可以实现多重继承,能够方便地复用代码、增强现有对象;文中特别针对对象方法的混合做了优化,使所有同名方法都能够执行。
深拷贝or浅拷贝?
数据类型
- 基本类型:Number、String、Boolean等;变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。
- 引用类型:Object、Array;变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。
拷贝仅针对引用类型数据
浅拷贝
const a = {
name: 'Json',
age: 18
}
const b = a; // a仅将自己保存的内存地址给了b,因此b和a同时指向同一块内存空间
console.log(a.name); // 'Json'
b.name = 'Table';
console.log(b.name); // 'Table'
console.log(a.name); // 'Table'
console.log(a === b); // true
深拷贝?
const a = {
name: 'Json',
age: 18,
children: {
name: 'Hello',
age: 1
}
}
const b = {...a} // 利用展开运算符将a展开,存入新内存中并将地址赋值给b
console.log(a.name); // 'Json'
b.name = 'Table';
console.log(b.name); // 'Table'
console.log(a.name); // 'Json'
console.log(a === b); // false
// 看起来很完美,修改b的属性并不会影响a,但是...
console.log(a.children.name); // 'Hello'
b.children.name = 'Table';
console.log(b.children.name); // 'Table'
console.log(a.children.name); // 'Table'
console.log(a.children === b.children); // true
类似对象展开和Object.assign()
方法拷贝的只是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
深拷贝!
我们不仅需要将容器对象重新制定内存地址,如果其中含有引用类型的子属性我们也要对其进行处理
/**
* 获取指定参数的原始类型
* @param {Any} o
*/
const getType = (o) => {
let _t;
return ((_t = typeof (o)) == "object" ? Object.prototype.toString.call(o).slice(8, -1) : _t).toLowerCase();
}
/**
* 拷贝对象
* @param {Object}
*/
const copyObj = (...arg) => {
// 参数个数
const length = arg.length;
// 使用||运算符,排除隐式强制类型转换为false的数据类型
// 如'', 0, undefined, null, false等
// 如果target为以上的值,则设置target = {}
let target = arg[0] || {};
// 是否使用深拷贝
let deep = false;
let i = 1;
let name;
let options;
let src;
let copy;
let copyIsArray;
let clone;
// 如果第一个参数的数据类型是Boolean类型,认为是设置拷贝模式
// target往后取第二个参数
if (getType(target) === 'boolean') {
deep = target;
target = arg[1] || {};
i++;
}
// 如果target不是一个对象或数组或函数
if (getType(target) !== 'object' && !(getType(target) === 'function')) {
target = {};
}
// 如果arg.length === 1 或
// typeof arg[0] === 'boolean',
// 且存在arg[1],则直接返回target对象
if (i === length) {
return target;
}
// 循环每个源对象
for (; i < length; i++) {
// 如果传入的源对象是null或undefined
// 则循环下一个源对象
options = arg[i];
if (typeof options != undefined) {
// 遍历所有[[emuerable]] === true的源对象
// 包括Object, Array, String
// 如果遇到源对象的数据类型为Boolean, Number
// for in循环会被跳过,不执行for in循环
for (name in options) {
// src用于判断target对象是否存在name属性
src = target[name];
// copy用于复制
copy = options[name];
// 判断copy是否是数组
copyIsArray = Array.isArray(copy);
if (deep && copy && (typeof copy === 'object' || copyIsArray)) {
if (copyIsArray) {
copyIsArray = false;
// 如果目标对象存在name属性且是一个数组
// 则使用目标对象的name属性,否则重新创建一个数组,用于复制
clone = src && Array.isArray(src) ? src : [];
} else {
// 如果目标对象存在name属性且是一个对象
// 则使用目标对象的name属性,否则重新创建一个对象,用于复制
clone = src && typeof src === 'object' ? src : {};
}
// 深复制,所以递归调用copyObject函数
// 返回值为target对象,即clone对象
// copy是一个源对象
target[name] = copyObj(deep, clone, copy);
} else if (copy !== undefined) {
// 浅复制,直接复制到target对象上
target[name] = copy;
}
}
}
}
// 返回目标对象
return target;
}
混合
但是有时我们可能想要将一些基础配置对象混入到其他地方,并且希望基础对象中的方法能够保留而不是覆盖。
方法混合
直接上优雅写法
Function.prototype.before = function (beforefn) {
let _self = this;
return function () {
beforefn.apply(this, arguments);
return _self.apply(this, arguments);
}
}
Function.prototype.after = function (afterfn) {
let _self = this;
return function () {
let ret = _self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
}
}
深拷贝 + 方法混合
有了方法混合,那么我们只需要在深拷贝中增加属性类型判断即可
const copyObjWithFunc = (...arg) => {
...
if (deep && copy && (typeof copy === 'object' || copyIsArray)) {
...
} else if (getType(copy) === 'function' && getType(target[name]) === 'function') {
// 函数复制,将copy方法追加在源方法后执行
// 这里在执行或之后都可以
target[name] = target[name].after(copy);
} else if (copy !== undefined) {
...
}
...
}
结果
Jsfiddle
https://jsfiddle.net/guchong/jcwysoth/3/
Comments
注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。