基础串联概念
- 基本数据类型:Number、String、Boolean、Null、 Undefined、Symbol(ES6);
- 引用数据类型:Object、Array、Function、Date、RegExp、Map、Set等。
柯里化的概念
在函数式编程(Functional Programming)相关的文章中,经常能看到 柯里化 (Currying)这个名词。它是数学家柯里(Haskell Curry)提出的。
柯里化,用一句话解释就是,把一个多参数的函数转化为单参数函数的方法。
这是一个两个参数的普通函数:
function plus(x, y){
return x + y
}
plus(1, 2) // 输出 3
经过柯里化后这个函数变成这样:
function plus(y){
return function (x){
return x + y
}
}
plus(1)(2) // 输出 3
什么是闭包
简单了解: 子函数可以调用父函数变量 闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 " 定义在一个函数内部的函数" 。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
2. 闭包的三大特性
内嵌函数:函数嵌套函数,内嵌函数对函数中的局部变量进行访问
局部变量:在函数内定义有共享意义的局部变量
外部使用:函数向外返回此内嵌函数,外部可通过此内嵌函数访问声明在函数中的局部变量,而此变量在外部是通过其他路径无法访问的
参数和变量不会立即被垃圾回收机制回收
3. 闭包的优点
可读取函数内部的变量
局部变量可以保存在内存中,实现数据共享
执行过程中所有变量都匿名在函数内部
4. 闭包的缺点
使函数内部变量存在于内存中,内存消耗大
滥用闭包可能导致内存泄露
闭包可以在父函数外部改变父函数内部的值,慎操作
5. 闭包的产生条件
作用域嵌套
在父级作用域里生成了一个变量 var i=0 在子作用域里使用这个变量,这样声明的那个变量 i 就是 自由变量,这种作用域嵌套环境叫做 闭包环境。
在内存中存在和回收站相似的机制,叫做 垃圾回收机制。自由变量在函数关闭后被放在垃圾回收机制里,当下次调用时,再重新出来。
6. 闭包的使用场景
模拟私有方法
setTimeout循环
匿名自执行函数
结果要缓存场景
实现类和继承
7. 使用闭包的注意点
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
8. 为什么要使用闭包?
使用闭包可以延长局部变量的生命周期,不让局部变量使用后立即释放,被删除。
// 当声明变量 i 在outFn 函数外面时,输出结果为:1,2,3,4
var i=0;
function outerFn(){
return function innerFn(){
i++;
console.log(i);
}
}
var fn1 = outFn();
fn1();
fn1();
var fn2 = outFn();
fn2();
fn2();
浅拷贝
var _ = require('lodash'); var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; var obj2 = _.cloneDeep(obj1); console.log(obj1.b.f === obj2.b.f);// false
- 浅拷贝:对于基本类型数据来说,拷贝的是值;对于引用数据来说,拷贝了地址,因此拷贝后的对象和原对象会共用一个内存地址,因此,属性值会同步变化(第二层往后的属性)
- 浅拷贝的方法:手写浅拷贝、Object.assign()、拓展运算符 对象是引用类型 如果被引用 引用改变了 它就改变了
常见的浅拷贝方法
- Object.assign()
## 1.Object.assign()
var obj1 = {
name: 'zhangsan',
age: 18,
sex: '男'
}
var obj2 = {}
for (var key in obj1) {
// obj1[key]
obj2[key] = obj1[key]
}
obj1.name = '李四'
console.log(obj2)
函数库lodash的_.clone方法
const oldObj = {
name: '马超',
age: 18
}
// const newObj = oldObj
// newObj.age = 20
// console.log(oldObj, newObj)
// Object.assgin()
// const newObj = {}
// Object.assign(newObj, oldObj) // 合并对象,生成一个新对象
// newObj.age = 80
// console.log(newObj, oldObj)
// {...}
// const obj = { ...oldObj }
// obj.age = 30
// console.log(oldObj, obj)
//3. 数组的浅拷贝
const arr = [1, 2, 3]
// const arr1 = [].concat(arr)
// arr1[1] = 20
// console.log(arr, arr1)
// 展开运算符
const newArr = [...arr]
newArr[2] = 20
console.log(newArr, arr)
3. 通过递归实现
// 1. JSON 序列化
// 2. lodash 库
// 3. 通过递归实现
const obj = {
name: '佩奇',
family: {
father: '猪爸爸'
},
job: ['跳泥坑', '唱歌']
}
// console.log(JSON.stringify(obj))
// console.log(JSON.parse(JSON.stringify(obj)))
// 序列化有问题 会忽略 function undefined
const newObj = JSON.parse(JSON.stringify(obj))
// console.log(newObj === obj)
newObj.family.father = 'dad'
console.log(obj)
console.log(newObj)
Array.prototype.concat()
let arr = [1, 3, { username: 'kobe' }]; let arr2 = arr.concat(); arr2[2].username = 'wade'; console.log(arr); //[ 1, 3, { username: 'wade' } ]
链接:https://juejin.cn/post/6844904197595332622
Array.prototype.slice()
let arr = [1, 3, { username: ' kobe' }];
let arr3 = arr.slice();
arr3[2].username = 'wade' console.log(arr); // [ 1, 3, { username: 'wade' } ]
深拷贝
.JSON.parse(JSON.stringify())
let arr = [1, 3, {
username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)
这也是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
函数库lodash的_.cloneDeep方法
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
jQuery.extend()方法
$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
var $ = require('jquery');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
手写递归方法
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
有种特殊情况需注意就是对象存在循环引用的情况,即对象的属性直接的引用了自身的情况,解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
函数库lodash的_.cloneDeep方法
该函数库也有提供_.cloneDeep用来做 Deep Copy
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
对于引用数据类型来说,就是拷贝原始对象的所有属性与方法,在内存中重新开辟一块内存空间进行存储
好了今天就分享到这里来,大家点点赞关注不迷路