js 拓展 柯里化与闭包 了解深拷贝浅拷贝 |掘金日新计划

84 阅读6分钟

基础串联概念

  • 基本数据类型: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

  1. 浅拷贝:对于基本类型数据来说,拷贝的是值;对于引用数据来说,拷贝了地址,因此拷贝后的对象和原对象会共用一个内存地址,因此,属性值会同步变化(第二层往后的属性)
  2. 浅拷贝的方法:手写浅拷贝、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

对于引用数据类型来说,就是拷贝原始对象的所有属性与方法,在内存中重新开辟一块内存空间进行存储

好了今天就分享到这里来,大家点点赞关注不迷路

AD1989B23755CC12C485F1C0418A9364.jpg