淺拷貝與深拷貝
淺拷貝
淺拷貝是創(chuàng)建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內(nèi)存地址,所以如果其中一個對象改變了這個地址,就會影響到另一個對象。
//實現(xiàn)淺拷貝
function shallowCopy (obj){
// 只拷貝對象,基本類型或null直接返回
if(typeof obj !== 'object' || obj === null) {
return obj;
}
// 判斷是新建一個數(shù)組還是對象
let newObj = Array.isArray(obj) ? []: {};
//for…in會遍歷對象的整個原型鏈,如果只考慮對象本身的屬性,需要搭配hasOwnProperty
for(let key in obj ){
//hasOwnProperty判斷是否是對象自身屬性,會忽略從原型鏈上繼承的屬性
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key];//只拷貝對象本身的屬性
}
}
return newObj;
}
//測試
var obj ={
name:'張三',
age:8,
pal:['王五','王六','王七']
}
let obj2 = shallowCopy(obj);
obj2.name = '李四'
obj2.pal[0] = '王麻子'
console.log(obj); //{age: 8, name: "張三", pal: ['王麻子', '王六', '王七']}
console.log(obj2); //{age: 8, name: "李四", pal: ['王麻子', '王六', '王七']}
測試結(jié)果:
深拷貝
深拷貝是將一個對象從內(nèi)存中完整的拷貝一份出來,從堆內(nèi)存中開辟一個新的區(qū)域存放新對象,且修改新對象不會影響原對象。
function deepCopy (obj, map = new WeakMap()){
// 基本類型或null直接返回
if(typeof obj !== 'object' || obj === null) {
return obj;
}
// 判斷是新建一個數(shù)組還是對象
let newObj = Array.isArray(obj) ? []: {};
//利用map解決循環(huán)引用
if (map.has(obj)) {
return map.get(obj);
}
map.set(obj, newObj);//將當(dāng)前對象作為key,克隆對象作為value
for(let key in obj ){
if(obj.hasOwnProperty(key)){
newObj[key] = deepCopy(obj[key], map); //遞歸
}
}
return newObj
}
// 測試
let obj1 = {
name : 'AK、噠噠噠',
arr : [1,[2,3],4],
};
let obj2=deepCopy(obj1)
obj2.name = "噠噠噠";
obj2.arr[1] = [5,6,7] ; // 新對象跟原對象不共享內(nèi)存
console.log('obj1',obj1) // obj1 { name: 'AK、噠噠噠', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: '噠噠噠', arr: [ 1, [ 5, 6, 7 ], 4 ] }
測試結(jié)果:
函數(shù)柯里化
函數(shù)柯里化指的是一種將使用多個參數(shù)的一個函數(shù)轉(zhuǎn)換成一系列使用一個參數(shù)的函數(shù)的技術(shù)。
作用:可以參數(shù)復(fù)用(公共的參數(shù)已經(jīng)通過柯里化預(yù)置了)和延遲執(zhí)行(柯里化時只是返回一個預(yù)置參數(shù)的新函數(shù),并沒有立刻執(zhí)行,在滿足條件后才會執(zhí)行)。
參數(shù)定長的柯里化
思路:通過函數(shù)的length屬性獲取函數(shù)的形參個數(shù),形參的個數(shù)就是所需參數(shù)的個數(shù)。維護(hù)一個數(shù)組,當(dāng)數(shù)組的長度與函數(shù)接收參數(shù)的個數(shù)一致,再執(zhí)行該函數(shù)。
// 實現(xiàn)函數(shù)柯里化
function curry(fn) {
// 返回一個新函數(shù)
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args); // 如果參數(shù)夠了,就執(zhí)行原函數(shù),返回結(jié)果
} else {
//返回一個新函數(shù),繼續(xù)遞歸去進(jìn)行柯里化,利用閉包,將當(dāng)前已經(jīng)傳入的參數(shù)保存下來
return function (...args2) {
//遞歸調(diào)用 curried 函數(shù)
return curried.apply(this, [...args, ...args2]); //新函數(shù)調(diào)用時會繼續(xù)傳參,拼接參數(shù)
};
}
};
}
// 測試
function sum(a, b, c) {
return a + b + c;
}
var curried = curry(sum);
console.log(curried(1, 2, 3)); //6
console.log(curried(1, 2)(3)); //6
console.log(curried(1)(2, 3)); //6
console.log(curried(1)(2)(3)); //6
測試結(jié)果:
參數(shù)不定長的柯里化
題目:如何實現(xiàn)一個方法,使計算結(jié)果能夠滿足如下預(yù)期。
add(1, 2, 3) // 6
add(1) // 1
add(1)(2) // 3
add(1, 2)(3) // 6
add(1)(2)(3) // 6
add(1)(2)(3)(4) // 10
思路:利用閉包和遞歸,如果參數(shù)為空,則判斷遞歸結(jié)束,求和,返回結(jié)果。
function addCurry() {
// 利用閉包的特性收集所有參數(shù)值
let arr = [...arguments]
//返回函數(shù)
return function fn() {
// 如果參數(shù)為空,則判斷遞歸結(jié)束,即傳入一個()執(zhí)行函數(shù)
if(arguments.length === 0) {
return arr.reduce((a, b) => a + b) // 求和
} else {
arr.push(...arguments)
return fn //遞歸
}
}
}
// 測試
console.log(addCurry(1)()); //1
console.log(addCurry(1)(2)()); //3
console.log(addCurry(1)(2)(3)()); //6
console.log(addCurry(1, 2)(3)()); //6
console.log(addCurry(1, 2, 3)()); //6
上述寫法,總是要以空括號()結(jié)尾,于是再改進(jìn)為隱式轉(zhuǎn)換.toString寫法,原理:當(dāng)用 Function的值做計算的時候,會調(diào)用toString做隱式轉(zhuǎn)換。注意一些舊版本的瀏覽器隱式轉(zhuǎn)換會默認(rèn)執(zhí)行,新版本不行了??梢岳秒[式轉(zhuǎn)換或者alert。
function addCurry() {
let arr = [...arguments]
// 利用閉包的特性收集所有參數(shù)值
var fn = function() {
arr.push(...arguments);
return fn;//遞歸
};
// 利用 toString 隱式轉(zhuǎn)換,轉(zhuǎn)換的時候再返回結(jié)果
fn.toString = function () {
return arr.reduce(function (a, b) {
return a + b;
});
}
return fn;
}
//測試
console.log(addCurry(1)(2) == 3) //true 利用隱式轉(zhuǎn)換,自動調(diào)用toString方法得到柯里化的結(jié)果
//alert(addCurry(1)(2)(3))//6 alert參數(shù)只能是字符串,如果其他類型的值,會轉(zhuǎn)換成字符串,會調(diào)用toString方法
console.log(addCurry(1).toString());//1 手動調(diào)用toString
console.log(addCurry(1, 2)(3).toString());//6
console.log(addCurry(1, 2)(3)(4)(5).toString());//15
測試結(jié)果:
數(shù)組扁平化
數(shù)組扁平化其實就是將多維數(shù)組轉(zhuǎn)為一維數(shù)組。
ES6中的flat
const arr = [1,[2,[3,[4,5]]],6]
// arr.flat([depth]) flat的參數(shù)代表的是需要展開幾層,如果是Infinity的話,就是不管嵌套幾層,全部都展開
console.log(arr.flat(Infinity)) //[1,2,3,4,5,6]
遞歸
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
//如果當(dāng)前元素還是一個數(shù)組
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));//遞歸拼接
} else {
result.push(arr[i]);
}
}
return result;
}
console.log(flatten(arr)); // [1, 2, 3, 4]
reduce函數(shù)迭代
從上面普通的遞歸函數(shù)中可以看出,其實就是對數(shù)組的每一項進(jìn)行處理,那么其實也可以用reduce來實現(xiàn)數(shù)組的拼接,從而簡化第一種方法的代碼。
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.reduce((total, cur) => {
return total.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []); //傳遞初始值空數(shù)組[],就會從數(shù)組索引為 0 的元素開始執(zhí)行
}
console.log(flatten(arr)); // [1, 2, 3, 4]
split和toString
數(shù)組的toString方法可以把數(shù)組直接轉(zhuǎn)換成逗號分隔的字符串。如[1, [2, [3, 4]]] => “1,2,3,4”
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
//先把數(shù)組直接轉(zhuǎn)換成逗號分隔的字符串,然后再用 split 方法把字符串重新轉(zhuǎn)換為數(shù)組
return arr.toString().split(",").map(Number);
}
console.log(flatten(arr)); // [ 1, 2, 3, 4 ]
數(shù)組去重
利用Set。new一個Set,參數(shù)為需要去重的數(shù)組,Set會自動刪除重復(fù)的元素,在Array.form將Set轉(zhuǎn)為數(shù)組返回
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log([...new Set(arr)]); //[ 1, 2, 3, 5, 9, 8 ]
console.log(Array.from(new Set(arr))); //[ 1, 2, 3, 5, 9, 8 ]
利用數(shù)組的filter()+indexOf去重。利用filter方法,返回arr.indexOf(num)等于index的值。原理就是indexOf會返回先找到的數(shù)字的索引。
function unique(arr) {
return arr.filter((item, index, array) => {
return array.indexOf(item) === index;
});
}
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log(unique(arr)); // [1, 2, 3, 5, 9, 8]
利用Map。新建一個數(shù)組和map,如果當(dāng)前值在map中沒有出現(xiàn)過,就加入數(shù)組,最后返回數(shù)組
const unique = (arr) => {
const map = new Map();
const res = [];
for (let item of arr) {
if (!map.has(item)) {
map.set(item, true);
res.push(item);
}
}
return res;
}
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log(unique(arr)); // [1, 2, 3, 5, 9, 8]
手寫類型判斷函數(shù)
思路:如果是null,直接返回String(null);基本類型和函數(shù),直接使用typeof;其它引用類型,使用Object.prototype.toString.call。文章來源:http://www.zghlxwxcb.cn/news/detail-858820.html
function getType(value) {
// 判斷數(shù)據(jù)是 null 的情況
let type;
if (value === null) {
return String(value);
}
// 判斷數(shù)據(jù)是基本數(shù)據(jù)類型的情況和函數(shù)的情況,使用typeof
if (typeof value !== "object") {
return typeof value;
} else {
// 判斷數(shù)據(jù)是引用類型的情況,設(shè)當(dāng)前類型為date
let valueClass = Object.prototype.toString.call(value); //"[object Date]"
type = valueClass.split(" ")[1].split(""); //[ 'D', 'a', 't', 'e', ']' ] 截取類型并轉(zhuǎn)換為數(shù)組
type.pop(); //[ 'D', 'a', 't', 'e' ],去掉數(shù)組最后的右括號"]"
return type.join("").toLowerCase(); //[ 'D', 'a', 't', 'e' ] => "Date" => "date" 數(shù)組轉(zhuǎn)小寫字符串
}
}
// 測試
console.info(getType(null)); // null
console.info(getType(undefined)); // undefined
console.info(getType(100)); // number
console.info(getType("abc")); // string
console.info(getType(true)); // boolean
console.info(getType(Symbol())); // symbol
console.info(getType({})); // object
console.info(getType([])); // array
console.info(getType(() => {})); // function
console.info(getType(new Date())); // date
console.info(getType(new RegExp(""))); // regexp
console.info(getType(new Map())); // map
console.info(getType(new Set())); // set
console.info(getType(new WeakMap())); // weakmap
console.info(getType(new WeakSet())); // weakset
console.info(getType(new Error())); // error
console.info(getType(new Promise(() => {}))); // promise
測試結(jié)果:文章來源地址http://www.zghlxwxcb.cn/news/detail-858820.html
到了這里,關(guān)于5個常見的前端手寫功能:淺拷貝與深拷貝、函數(shù)柯里化、數(shù)組扁平化、數(shù)組去重、手寫類型判斷函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!