• 1. JavaScript数据类型

    • 1.1. typeof返回值
    • 1.2. let、const
    • 1.3. 堆内存、栈内存
  • 2. shallowCopy

    • 2.1. Object.assign()
    • 2.2. 拓展运算符
    • 2.3. Array.prototype.slice()
    • 2.4. Array.prototype.concat()
    • 2.5. Array.from()
  • 3. deepClone

    • 3.1. JSON实现
    • 3.2. 递归实现
    • 3.3. 第三方库实现
    • 3.4. Structured Clone 结构化克隆算法
  • 4. 对于上一篇文章——Functions Setter
  • 5. 附加
  • 6. Clone总结

 1. JavaScript数据类型

当初的ECMAScript有7种根本数据类型,一种援用数据类型。参考:JavaScript 数据类型和数据结构

7 种原始类型:

  1. Boolean
  2. Null
  3. Undefined
  4. Number
  5. BigInt
  6. String
  7. Symbol

和援用数据类型

  1. Object

这里只写了一种,然而还有很多其余也是援用数据类型,比方Array、Function、Date、RegExp、Error。ECMAScript不反对任何创立自定义类型的机制,就好比C语言的自定义构造体,这是不反对的。

ES6减少了一种根本数据类型Symbol,数据类型 “symbol” 是一种原始数据类型,该类型的性质在于这个类型的值能够用来创立匿名的对象属性。该数据类型通常被用作一个对象属性的键值——当你想让它是公有的时候,详见 MDN——symbol

当初还有一种BigInt数据类型,参考 MDN——BigInt

 1.1. typeof返回值

返回八种数据类型,参考:MDN——typeof

null会返回object,function会返回function

| 返回值(字符串) |

| :--------------: |

|   "undefined"    |

|     "object"     |

|    "boolean"     |

|     "number"     |

|     "bigint"     |

|     "string"     |

|     "symbol"     |

|    "function"    |

注:强调typeof是一个操作符而非函数,括号可略,null之所以会返回object是因为null最后是作为空对象的占位符的应用的,被认为是空对象的援用。

实际上undefined值派生自null值,所以undefined == null //true

如果定义的变量未来用于保留对象,那么最好将该变量初始化为null,这样只有查看null值就能够晓得相应的变量是否曾经保留了一个对象的援用。

【例】

 var car = null; if (car != null) { //操作 }
注:只管null和undefined有非凡关系,但他们齐全不同,任何状况都没有必要把一个变量值显式地设置为undefined,但对null并不实用,只有意在保留对象的变量还没有真正保留对象,就应该明确保留变量为null值。这样不仅体现null作为空对象指针的常规,也有助于进一步辨别null和undefined。

 1.2. let、const

  • const:常量是块级作用域,很像应用 let 语句定义的变量。const定义的变量不能从新申明,不能通过赋值扭转
  • let:语句申明一个块级作用域的本地变量,并且可选的将其初始化为一个值
 let x = 1; if (x === 1) { let x = 2; console.log(x); // expected output: 2 } console.log(x); // expected output: 1

 1.3. 堆内存、栈内存

当变量复制援用类型值的时候,它是一个指针,指向存储在堆内存中的对象(堆内存中的对象无奈间接拜访,要通过这个对象在堆内存中的地址拜访,再通过地址去查值(RHS查问,试图获取变量的源值),所以援用类型的值是按援用拜访)

变量的值也就是这个指针(我的意思是这个指针是原始值)是存储在栈上的,当变量obj1复制变量的值给变量obj2时,obj1,obj2只是一个保留在栈中的指针,指向同一个存储在堆内存中的对象,所以当通过变量obj1操作堆内存的对象时,obj2也会一起扭转

 2. shallowCopy

已经在面试中遇到过这个问题,面试官问我深拷 贝与浅拷贝的区别

根本数据类型并没有深浅拷贝之分,次要是援用类型数据,援用类型数据 的浅拷贝会创立一个新对象,它有着被拷贝对象属性值的一份准确拷贝。拷贝的是内存地址,所以其中一个值的变动会在另一个下面反映进去。

let obj1 = { a: 1 };let obj2 = obj1;obj2.a = 2;console.log(obj1); //{a:2}console.log(obj2); //{a:2}

 2.1. Object.assign()

Object.assign()办法用于将所有可枚举属性的值从一个或多个源对象复制到指标对象。它将返回指标对象。如果指标对象中的属性具备雷同的键,则这些属性将被源对象中的属性笼罩。(有多个源对象时)前面的源对象的属性将相似地笼罩后面的源对象的属性。该办法只会拷贝源对象本身的 并且 可枚举的属性到指标对象。

该办法应用源对象的[[Get]](取得值)和指标对象的[[Set]](设置值)(这两个在上一篇文章讲过),所以它会调用相干 gettersetter。因而,它调配属性,而不仅仅是复制或定义新的属性。如果合并源蕴含getter,这可能使其不适宜将新属性合并到原型中。为了将属性定义(包含其可枚举性)复制到原型,应应用Object.getOwnPropertyDescriptor(obj, prop)Object.defineProperty()

  1. Object.getOwnPropertyDescriptor(obj, prop):返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是间接赋予该对象的属性,不须要从原型链上进行查找的属性)其中obj——须要查找的指标对象;prop——指标对象内属性名称(字符串)
  2. Object.defineProperty(obj, prop, descriptor):办法会间接在一个对象上定义一个新属性,或者批改一个对象的现有属性,并返回这个对象;其中obj——要在其上定义属性的对象。prop——要定义或批改的属性的名称。descriptor——将被定义或批改的属性描述符。返回值是被传递给函数的对象。

String类型和 Symbol 类型的属性都会被拷贝。

在呈现谬误的状况下,例如,如果属性不可写,会引发TypeError,如果在引发谬误之前增加了任何属性,则能够更改target对象。

留神,Object.assign 不会在那些source对象值为 null 或 undefined 的时候抛出谬误。

 let target = { a: 1, b: 2 }; let source = { b: 4, c: 5 }; let returnedTarget = Object.assign(target, source); target.a = 111; console.log(target); //  output: Object { a: 111, b: 4, c: 5 } console.log(returnedTarget); //  output: Object { a: 111, b: 4, c: 5 }

输入后果相互影响阐明这是一个浅拷贝

  1. 继承属性和不可枚举属性是不能拷贝的
  2. 属性的数据属性/拜访器属性
  3. 能够拷贝Symbol类型
  4. 原始类型会被包装为对象
  5. 异样会打断后续拷贝工作
 let obj1 = { a: { b: 1 }, sym: Symbol(1) } Object.defineProperty(obj1, 'innumerable', { value: '不可枚举属性', enumerable: false }) let obj2 = {}; Object.assign(obj2, obj1); obj1.a.b = 2; console.log("obj1", obj1); //obj1 {a: {…}, sym: Symbol(1), innumerable: "不可枚举属性"} console.log("obj2", obj2); //obj2 {a: {…}, sym: Symbol(1)}

MDN——Object.defineProperty()

合并对象或者合并具备雷同属性的对象

 const o1 = { a: 1 } const o2 = { a: 2 } const o3 = { a: 3 } const obj = Object.assign(o1, o2, o3); console.log(obj); //{a: 3} console.log(o1); //{a: 3} const o4 = { a: 32 }; const obj2 = Object.assign(o1, o2, o3, o4); console.log(obj2); //{a: 32}

 2.2. 拓展运算符

 let obj1 = { a: 1, b: { c: 1, } }; let obj2 = { ...obj1 }; obj1.a = 2; console.log(obj2); //{a: 1,b:{c: 1,}} console.log(obj1.a === obj2.a); //false console.log(obj1.b === obj2.b); //true // 拓展运算符对根本数据类型间接创立新值,对援用数据类型shallowcopy
 let obj1 = { a: 1, b: { c: 1, d: ["a", { e: 1 }] } }; let obj2 = { ...obj1 }; obj1.b.d[1].e = 2; console.log(obj1.a === obj2.a); console.log(obj1.b.d[1].e === obj2.b.d[1].e); //true // 阐明拓展运算符只实用于对根本数据类型的值进行shallowcopy

扩大运算符Object.assign()有同样的缺点,对于值是对象的属性无奈齐全拷贝成2个不同对象(只是拷贝一份援用),然而如果属性都是根本类型的值的话,应用扩大运算符更加不便。

 2.3. Array.prototype.slice()

MDN——Array.prototype.slice()

slice() 办法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包含 begin,不包含end)。原始数组不会被扭转。

语法arr.slice([begin[, end]])也就是说只写一个参数的时候是begin,若省略两个参数将会从头到尾索引

  • begin(可选);提取起始处的索引(从 0 开始),从该索引开始提取原数组元素。

如果该参数为正数,则示意从原数组中的倒数第几个元素开始提取,slice(-2) 示意提取原数组中的倒数第二个元素到最初一个元素(蕴含最初一个元素)。如果省略 begin,则 slice 从索引 0 开始。如果 begin 大于原数组的长度,则会返回空数组。

  • end(可选):提取终止处的索引(从 0 开始),在该索引处完结提取原数组元素。slice 会提取原数组中索引从 beginend 的所有元素(蕴含 begin,但不蕴含 end)。slice(1,4) 会提取原数组中从第二个元素开始始终到第四个元素的所有元素 (索引为 1, 2, 3的元素)。如果该参数为正数, 则它示意在原数组中的倒数第几个元素完结抽取。 slice(-2,-1) 示意抽取了原数组中的倒数第二个元素到最初一个元素(不蕴含最初一个元素,也就是只有倒数第二个元素)。如果 end 被省略,则 slice 会始终提取到原数组开端。如果 end 大于数组的长度,slice 也会始终提取到原数组开端。
  • 返回值:一个含有被提取元素的新数组

形容:slice 不会批改原数组,只会返回一个shallowCopy了原数组中的元素的一个新数组。原数组的元素会依照下述规定拷贝:如果该元素是个对象援用 (不是理论的对象),slice 会拷贝这个对象援用到新的数组里。两个对象援用都援用了同一个对象。如果被援用的对象产生扭转,则新的和原来的数组中的这个元素也会产生扭转。对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里批改这些字符串或数字或是布尔值,将不会影响另一个数组。如果向两个数组任一中增加了新元素,则另一个不会受到影响。

 2.4. Array.prototype.concat()

语法var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])valueN可选将数组和/或值连接成新数组。如果省略了valueN参数参数,则concat会返回一个它所调用的已存在的数组的浅拷贝。 返回值:新的 Array 实例。

concat() 办法用于合并两个或多个数组。此办法不会更改现有数组,而是返回一个新数组。

 let old_array = ["Hui", { name: "Dong" }]; let arr1 = [1, 2, 3]; let arr2 = [4, 5]; let valueN = [6, 7]; let new_array = old_array.concat(arr1, arr2, ...valueN) console.log(new_array); //["Hui", {name: "Dong"}, 1, 2, 3, 4, 5, 6, 7]

 2.5. Array.from()

Array.from() 办法从一个相似数组或可迭代对象创立一个新的,浅拷贝的数组实例。

 console.log(Array.from('foo')); // expected output: Array ["f", "o", "o"]A Array.from(('Tian'), x => x + "1") // expected return: (4) ["T1", "i1", "a1", "n1"]

 3. deepClone

 3.1. JSON实现

能够先理解JSON.stringify() (将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串)和 JSON.parse() (解析JSON字符串,结构由字符串形容的JavaScript值或对象)

简而言之就是先应用stringify将对象转为JSON字符串再应用parse解析成对象

 let obj1 = { a: 0, b: { c: 0 } } let obj2 = Object.assign({}, obj1); console.log(JSON.stringify(obj2)); //{"a":0,"b":{"c":0}} obj1.a = 1; console.log(JSON.stringify(obj1)); //{"a":1,"b":{"c":0}} console.log(JSON.stringify(obj2)); //{"a":0,"b":{"c":0}} obj2.a = 2; console.log(JSON.stringify(obj1)); //{"a":1,"b":{"c":0}} console.log(JSON.stringify(obj2)); //{"a":2,"b":{"c":0}} obj2.b.c = 3; console.log(JSON.stringify(obj1)); //{"a":1,"b":{"c":3}} console.log(JSON.stringify(obj2)); //{"a":2,"b":{"c":3}} // DeepClone obj1 = { a: 0, b: { c: 0 } } let obj3 = JSON.parse(JSON.stringify(obj1)); console.log(obj3.a === obj1.a); //true console.log(obj3.b === obj1.b); //true console.log(obj3 === obj1); //false obj1.a = 4; obj1.b.c = 4; console.log(JSON.stringify(obj3)); //{"a":0,"b":{"c":0}}

 3.2. 递归实现

两个办法,第二个源于yeyan1996的博客
 function deepClone(origin, target1) { let target2 = target1 || {}; for (let prop in origin) { if (origin.hasOwnProperty(prop)) { if (origin[prop] !== 'null' && typeof (origin[prop]) == 'object') { if ( Object.prototype.toString.call(origin[prop]) == '[object Array]' ) { target2[prop] = []; } else { target2[prop] = {}; } deepClone(origin[prop], target2[prop]); } else { target2[prop] = origin[prop]; } } } return target2; }
留神其中传target时,他必须是个object型的数据
 let obj1 = { a: { b: 1 } } function deepClone(obj) { let cloneObj = {}; //在堆内存中新建一个对象 for (let key in obj) { //遍历参数的键 if (typeof obj[key] === 'object') { cloneObj[key] = deepClone(obj[key]) //值是对象就再次调用函数 } else { cloneObj[key] = obj[key] //根本类型间接复制值 } } return cloneObj } let obj2 = deepClone(obj1); obj1.a.b = 2; console.log(obj2); //{a:{b:1}}

 3.3. 第三方库实现

  • jQuery.extend():将两个或者更多个对象的内容合并到第一个对象

语法1jQuery.extend( target [, object1 ] [, objectN ] )

target→类型: Object,如果附加的对象被传递给这个办法将那么它将接管新的属性,如果它是惟一的参数将扩大jQuery的命名空间。

object1→类型: Object,它蕴含额定的属性合并到第一个参数

objectN→类型: Object,蕴含额定的属性合并到第一个参数

语法2jQuery.extend( [deep ], target, object1 [, objectN ] )

deep→类型: Boolean,如果是true,合并成为递归(又叫做深拷贝)。

target→类型: Object,对象扩大。这将接管新的属性。

object1→类型: Object,一个对象,它蕴含额定的属性合并到第一个参数.

objectN→类型: Object,蕴含额定的属性合并到第一个参数

  • lodash

 3.4. Structured Clone 结构化克隆算法

参考:jessezhao1990→JavaScript 深拷贝

 function structuralClone(obj) { return new Promise(resolve => { const { port1, port2 } = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }) } let obj = { name: 'Tian', age: 22, wife: { name: '田甜' }, fun: ['HFUT', '电信'] }; structuralClone(obj).then(res => { console.log(res); }) obj.wife.name = "哈哈"; structuralClone(obj).then(res => { console.log(res); })

 4. 对于上一篇文章——Functions Setter

参考:MDN解释

当尝试设置属性时,set语法将对象属性绑定到要调用的函数。

  • get一个给属性提供 getter 的办法,如果没有 getter 则为 undefined。当拜访该属性时,该办法会被执行,办法执行时没有参数传入,然而会传入this对象(因为继承关系,这里的this并不一定是定义该属性的对象)。默认为 undefined。
  • set一个给属性提供 setter 的办法,如果没有 setter 则为 undefined。当属性值批改时,触发执行该办法。该办法将承受惟一参数,即该属性新的参数值。

默认为 undefined

语法:{set prop(val) { . . . }}{set [expression](val) { . . . }}

prop:要绑定到给定函数的属性名。

val:用于保留尝试调配给prop的值的变量的一个别名。

表达式:从 ECMAScript 2015 开始,还能够应用一个计算属性名的表达式绑定到给定的函数。

 const language = { //定义language为一个对象 set current(name) { //我认为current就像一个函数,或者说language的一个办法 this.log.push(name); //把name推到log数组中去 console.log(name); }, log: [], //是个数组 }; language.current = "EN"; language.current = "FA"; console.log(language.log); //["EN", "FA"] console.log(language.name); //undefined,为什么?

 5. 附加

  1. map() 办法创立一个新数组,其后果是该数组中的每个元素都调用一个提供的函数后返回的后果。
  2. 箭头函数表达式的语法比函数表达式更简洁,并且没有本人的this,arguments,super或new.target。箭头函数表达式更实用于那些原本须要匿名函数的中央,并且它不能用作构造函数。
  3. Promise 对象用于示意一个异步操作的最终实现 (或失败), 及其后果值。
  4. 三目运算:var num = 1 > 0 ? ("10" > "9" ? 1 : 0) : 2; //输入0数字字符串与数字比会转换为数字,然而字符串与字符串比会从左到右逐位相比,首先1就不大于9,所以输入0,再举个例子:"21">"199"//输入true因为2先和1比间接就true了。联合三目运算能够简化上述本人实现的克隆办法,如果把Object.prototype.toString这样的办法用变量来代替会更加简洁
 function deepClone(origin, target) { var target = target || {}; for (var prop in origin) { if (origin.hasOwnProperty(prop)) { origin[prop] !== 'null' && typeof (origin[prop]) == 'object' ? (Object.prototype.toString.call( origin[prop]) == '[object Array]' ? target[prop] = [] : target[prop] = {}, deepClone(origin[prop], target[ prop])) : target[ prop] = origin[prop]; } } return target; }

 6. Clone总结

根本数据类型没有深浅拷贝拷贝之分,对于援用数据类型,浅拷贝指的是拷贝援用,所以拷贝之后会有两个对象同时指向一个内存

深拷贝则是齐全复制其相应的键和值,拷贝之后两个object就没有了分割。文中给出了局部办法并且思考不全,对于一些非凡的场景还需重新考虑。