- 关于对象深拷贝的手写
- 关于对象合并的方案
- 函数柯里化
- queryURLParams实现
- AOP面向切片编程
浅拷贝
浅拷贝:一个新的对象对原始对象的属性值进行精确的拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生改变。
- Object.assign()
- 扩展运算符
- 数组方法实现
Array.prototype.slice/concat,注:concat方法不会改变原数组,而是返回新数组。
数组的浅拷贝
let newArr = [...arr]
newArr = arr.concat([])
newArr = arr.slice()
若是引用数据类型,拷贝出来的数据依然有关联:
对象的浅拷贝
function shallowClone(obj) {
if (!obj || typeof obj !== 'object) return
let newObj = Array.isArray(object) ? [] : {}
for (let key in object) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
对象的深拷贝
JSON.parse(JSON.stringify()),无法处理函数、正则、时间格式、原型上的属性和方法- 递归实现
注意:循环引用和多个属性引用同一个对象(重复拷贝)的问题
// 循环拷贝:对象的属性引用自己
let obj = {name: 'lihua'}
obj.obj = obj
重复拷贝问题
let obj = {}
let obj1 = {a: obj, b: obj}
实现深拷贝
- 使用hash 存储已拷贝过的对象,避免循环拷贝和重复拷贝
- 兼容数组和对象
- 避免重复拷贝
function isObject(target) {
return typeof target === "object" && target !== null;
}
function deepClone(target, hash = new WeakMap()) {
if (!isObject(target)) return target;
if (hash.get(target)) return hash.get(target);
// 兼容数组和对象
let newObj = Array.isArray(target) ? [] : {};
// 关键代码,解决对象的属性循环引用 和 多个属性引用同一个对象的问题,避免重复拷贝
hash.set(target, newObj);
for (let key in target) {
if (target.hasOwnProperty(key)) {
if (isObject(target[key])) {
newObj[key] = deepClone(target[key], hash); // 递归拷贝
} else {
newObj[key] = target[key];
}
}
}
return newObj;
}
循环引用示例
let obj = { a: 1 };
let obj1 = {
key1: info,
key2: info,
list: [1, 2],
};
obj1.key3 = obj1;
let val = deepClone(obj1);
console.log(val);
两个对象的合并
几种情况的分析:
- A:options中的key值,B:params中的key值
- A&B都是原始值类型:B替换A即可
- A是对象&B是原始值:抛出异常信息
- A是原始值&B是对象:B替换A即可
- A&B都是对象:依次遍历B中的每一项,替换A中的内容
const options = {
url: "",
method: "GET",
headers: {
"Content-Type": "application/json",
},
data: null,
arr: [10, 20, 30],
config: {
xhr: {
async: true,
cache: false,
},
},
};
const params = {
url: "http://www.xxx.cn/api/",
headers: {
"X-Token": "EF00F987DCFA6D31",
},
data: {
lx: 1,
from: "weixin",
},
arr: [30, 40],
config: {
xhr: {
cache: true,
},
},
};
function isObj(target) {
return typeof target === "object" && target !== null;
}
function merge(options, params = {}) {
for (let key in params) {
let isA = isObj(options[key]),
isB = isObj(params[key]);
// A是对象&B是原始值:抛出异常信息
if (isA && !isB) throw new TypeError(`${key} in params must be object`);
// A&B都是对象:依次遍历B中的每一项,替换A中的内容
if (isA && isB) {
options[key] = merge(options[key], params[key]);
}
// A&B都是原始值类型:B替换A即可
options[key] = params[key];
}
return options;
}
console.log(merge(options, params));
打印结果:
函数柯里化
概念:将使用多个参数的一个函数,转换成一系列使用一个参数的函数
原理:用闭包把参数保存起来,当参数的长度等于原函数时,就开始执行原函数
function mycurry(fn) {
// fn.length 表示函数中参数的长度
// 函数的length属性,表示形参的个数,不包含剩余参数,仅包括第一个有默认值之前的参数个数(不包含有默认值的参数)
if (fn.length <= 1) return fn;
// 自定义generator迭代器
const generator = (...args) => {
// 判断已传的参数与函数定义的参数个数是否相等
if (fn.length === args.length) {
return fn(...args);
} else {
// 不相等,继续迭代
return (...args1) => {
return generator(...args, ...args1);
};
}
};
return generator;
}
function fn(a, b, c, d) {
return a + b + c + d;
}
let fn1 = mycurry(fn);
console.log(fn1(1)(2)(3)(4));
打印结果:10
queryURLParams实现
parseParam函数接受一个 URL 作为输入,然后从 URL 中提取查询参数部分(即?后面的部分)。paramsStr变量通过正则表达式提取出查询参数部分。paramsArr变量将提取出来的查询参数部分以&分割,并存储为一个数组,每个元素都是一个查询参数。paramsObj变量用于存储最终解析后的参数对象。paramsArr数组被遍历,对每个查询参数进行处理:- 如果参数包含
=,表示它带有值,那么将键值对分割并解码。 - 如果值是数字字符串,将其转换为数字。
- 如果
paramsObj中已经存在相同的键,则将值存为数组(因为一个键可以有多个值)。 - 如果没有值(即没有
=),则将键存为true。
- 如果参数包含
- 最后,函数返回解析后的
paramsObj对象。
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 处理有 value 的参数
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else { // 处理没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}
let url =
"http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled";
console.log(parseParam(url));
结果为:
{ user: 'anonymous', id: [ 123, 456 ], city: '北京', enabled: true }
AOP面向切面编程
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过”动态织入“的方式掺入业务逻辑模块中.
before方法的实现是,在原函数执行之前执行传入的回调函数。它创建了一个新的代理函数proxy,这个代理函数首先执行传入的回调函数,然后再调用原函数,传递相同的参数。这样就实现了在原函数执行前添加功能的目的。after方法的实现是,在原函数执行之后执行传入的回调函数。类似于before方法,它也创建了一个代理函数proxy。不过这次,它先调用原函数,获取到其返回值,并将其保存在res变量中。然后,它执行传入的回调函数,并返回之前保存的原函数返回值。
Function.prototype.before = function before(callback) {
if (typeof callback !== "function")
throw new TypeError("callback must be function");
// this->func
let _self = this;
return function proxy(...params) {
// this !== func 调用时候才知道
//控制callback和func本身的先后执行顺序
callback.call(this, ...params);
return _self.call(this, ...params);
};
};
Function.prototype.after = function after(callback) {
if (typeof callback !== "function")
throw new TypeError("callback must be function");
let _self = this;
return function proxy(...params) {
let res = _self.call(this, ...params);
callback.call(this, ...params);
return res;
};
};
let func = () => {
// 主要的业务逻辑
console.log("func");
};
/* func.before(() => {
console.log('===before===');
})(); */
func
.before(() => {
console.log("===before===");
})
.after(() => {
console.log("===after===");
})();
打印结果: