一、以下是一份完整的深拷贝的代码:

  1. const isComplexType = function(obj) { // 判断这个数据类型是不是对象,并且不是null
  2. return (typeof obj === 'object' || typeof obj === 'function') && obj !== null;
  3. }
  4. function deepClone(obj, hash = new WeakMap()) {
  5. const type = [ Date, RegExp, Set, Map, WeakMap, WeakSet ]; // 需要特殊处理的数据类型
  6. if (type.includes(obj.contructor)) {
  7. return new obj.constructor(obj);
  8. }
  9. if (hash.has(obj)) { // 处理循环引用
  10. return hash.get(obj);
  11. }
  12. // 获取传入参数所有键的特性
  13. const allDesc = Object.getOwnPropertyDescriptors(obj);
  14. // 根据原型对象创拷贝内容,继承了原型属性和属性的描述
  15. const cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);
  16. hash.set(obj, cloneObj);
  17. // Relect.ownkeys() 可以枚举不可以枚举的类型和符号
  18. for (let key of Relect.ownkeys(obj)) {
  19. cloneObj[key] = (isComplexType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key];
  20. }
  21. return cloneObj
  22. }

问题如下:
  • Object.getPrototypeOf的用途
  • Object.cteate的用途
  • Object.getOwnPropertyDescriptors的用途
  • Map和WeakMap的区别
  • Reflect.ownkeys()用途以及和for in的区别,Reflect和proxy相关内容了解
  • Symbol是什么东西
  • 什么是元编程
    以上问题我们来一一解答。

    二、Object.getPrototypeOf的用途

    用法:Object.getPrototypeOf(obj);
    返回指定对象的原型,可以理解为返回对象的隐式原型,和它的构造函数的显式原型相等。
    obj:要返回其原型的对象。
    返回值:给定对象的原型。如果没有继承属性,则返回null。
    既然提到了原型,那就以下方神图镇一下
    image.png
    补充:Object.setPrototypeOf(obj, prototype),该方法表示obj对象要设置其原型对象。prototype是该对象的新原型。它的返回值是指定的对象。

    三、Object.create的用途

    用法:Object.create(proto, [propertiesObject]),第一个参数为新创建对象的原型对象和属性,第二个参数为一个对象,该传入对象的自有属性将为新对象添加指定的属性值和对应的属性描述。
    可以通过Object.create的方式来实现继承,代码如下:
    1. // 寄生组合继承的方式和组合继承的区别在于,在子类调用父类,但是不需要子类的原型再一次调用父类的构造函数
    2. function Parent(value) { // 父类
    3. this.value = value;
    4. }
    5. Parent.prototype.getValue = function() {
    6. return this.value;
    7. }
    8. function Child(value) { // 子类
    9. Parent.call(this, value);
    10. }
    11. Child.prototype = Object.create(Parent.prototype, {
    12. constructor: {
    13. value: Child,
    14. enumerable: false,
    15. writable: false,
    16. configurable: false
    17. }
    18. })

    四、Object.getOwnPropertyDescriptors的用途

    用法:Object.getOwenPropertyDescriptors(obj),该方法用来获取一个对象的所有自身属性的描述符,返回值为所指定对象的所有自身属性的描述符,如果没有任何自身属性,就返回空对象。
    可以通过Object.getOwnPropertyDescriptors和Object.create的方式来创建进行浅拷贝一个对象,如果用object.assign方法只能拷贝源对象的可枚举的自身属性,同时拷贝时无法拷贝属性的特性,服务拷贝纸原型,而且访问属性被转成数据属性。
    以下是基本应用
    1. var a = {
    2. b: 1,
    3. get c() {
    4. return 2
    5. }
    6. }
    7. var k = Object.getOwnPropertyDescriptors(a);
    8. /**
    9. 输出结果:就是返回对象内部属性的描述
    10. k = {
    11. b: {
    12. configurable: true
    13. enumerable: true
    14. value: 1
    15. writable: true
    16. },
    17. c: {
    18. configurable: true
    19. enumerable: true
    20. get: ƒ c()
    21. set: undefined
    22. }
    23. }
    24. */
    25. // 可以通过一下形式进行拷贝对象,可以得到一个原型相同,并且属性具有描述的对象
    26. Object.create(Object.getPrototypeOf(a), Object.getOwnPropertyDescriptors(a));

    五、Map和WeakMap的区别

    首先说一下map和object的区别,object对象的key值只能是string或者symbol类型,map对象的key值可以是任意类型(可以是基础类型,也可以是引用类型)。map和weakMap的区别是,weakMap的key值只能是引用类型数据,并且weakMap的key的引用还是一个弱引用,可以被垃圾回收机制回收。

    六、Reflect.ownkeys以及周边Reflect相关内容

    Reflect.ownkeys(),返回一个包含自身所有属性的数组(不包含继承属性), 不管属性名是symbol还是string都可以进行获取,也不受enumable的影响。它和for in的区别在于,for in不可以遍历symbol的属性名,Reflect.ownkeys无论属性名是什么数据类型都可以进行获取。
    Reflect的相关内容:
    Reflect是一个内置的对象,他提供拦截javascript操作方法。Reflect不是一个函数对象,因此它是不可构造的。
    Reflect与大多数全局对象不同,Reflect并非一个构造函数,所以它不能通过new运算符对其进行调用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的。

Reflect的静态方法:

1、Reflect.apply(target, thisArgument, argumentList),对一个函数进行调用,同时可以传入一个数组作为调用参数。和Function.prototype.apply()功能类似。

2、Reflect.construct(target, argument[, newTarget]),对构造函数进行new操作,相当于执行了

new target(…args)。

3、Reflect.defineProperty(target, properkey, attribute)和Object.defineProperty类似。如果设置成功就会返回true。

4、Reflect.deleteProperty(target, properkey),作为函数的delete操作符,相当于执行了

delete target[name]。

5、Reflect.get(target, properkey[, receiver]),获取对象身上某个属性的值,类似于target[name]。

6、Reflect.getOwnPropertyDescriptor(target, properkey),类似于Object.getOwnPropertyDescriptor(),如果对象中存在该属性,则返回对应属性描述符,否则返回undefined。

7、Reflect.getPrototypeOf(target),类似于Object.getPrototypeOf,返回对象的原型对象。

8、Reflect.has(target, properkey),判断一个对象是否存在某个属性,和in运算符功能相同。

9、Reflect.ownKeys(target),返回一个包含所有自身属性( 不包含继承属性)的数组。

10、Reflect.set(target, properkey, value[, receviver]),将值分配给属性的函数,返回一个boolean,如果成功返回true。

七、Symbol与元编程

1、基本概念

在ES6之前的javascript的数据类型有undefined、null、string、number、boolean、object,现在symbol最为第七种基本数据类型。表示独一无二唯一的,最多的用法就是用来定义对象的唯一属性名。

2、Symbol的基本用法

Symbol函数不能用new命令,因为Symbol是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的Symbol提供描述。
  1. let sy = Symbol('one');
  2. console.log(sy); // Symbol(one)
  3. typeof(sy); // Symbol
  4. // 相同参数Symbol()返回值是不相同的
  5. let sy1 = Symbol('one');
  6. console.log(sy === sy1); // false

3、Symbol.for

symbol.for类似于单例模式,首先会在全局搜索被登记的Symbol中是否有该字符串参数作为名称的Symbol值,如果有即返回该Symbol的值,若没有则新建并返回一个以该字符串为名称的Symbol值,并登记在全局环境中以供搜索。(注意:Symbol.for只能查询使用Symbol.for全局登记的Symbol,不能使用Symbol登记的)。
  1. let yellow = Symbol('yello');
  2. let yellow1 = Symbol.for('yello');
  3. console.log(yellow === yellow1); // false
  4. let yellow2 = Symbol.for('yellow');
  5. console.log(yellow1 === yellow2); // true

4、Symbol.keyFor

Symbol.keyFor返回一个已经登记的Symbol类型的key值,用来检测该字符串作为参数名称的Symbol是否进行过登记。(注意:这个注册的Symbol需要通过Symbol.for的方式进行注册)
  1. let yellow1 = Symbol.for('yellow');
  2. Symbol.keyFor(yellow1); // yellow

5、Symbol的应用场景

场景一:使用Symbol值作为对象的属性名
  1. let obj = {
  2. num: 123,
  3. text: 'abc'
  4. }
  5. console.log(obj['num']); // 123
  6. console.log(obj['text']); // abc
  7. const osNum = Symbol('num');
  8. const osText = Symbol('text');
  9. let objS = {
  10. [osNum]: 123,
  11. [osText]: 'abc'
  12. }
  13. console.log(objS[osNum]); // 123
  14. console.log(objS[osText]); // abc

看似简单的应用场景,其实这里隐藏了好几个值得注意的问题,使用Symbol值作为对象的属性名不能被for in和for of正常枚举,不能使用Object.keys、Object.getOwnPropertyNames正常获取属性名,但是Object.getOwnPropertyDescriptor获取对象属性描述中enumerable的值为true,也就是说从对象的描述符所表示的来看Symbol值作为对象属性名是可以被枚举的,但是需要一个特定的枚举方式来实现:Object.getOwnPropertySymbols和Reflect.ownkeys的方式来进行获取。
  1. for (var key in objS) {
  2. console.log(key); // 无输出
  3. }
  4. Object.keys(obj); // ['num', 'text']
  5. Object.keys(objS); // []
  6. Object.getOwnPropertyNames(obj); // ['num', 'text']
  7. Object.getOwnPropertyNames(objS); // []
  8. Object.getOwnPropertyDescriptor(objS, osNum); // [ value: 123, writable: true, emunerabel: true, configurable: true]
  9. Object.getOwnPropertyDescriptors(objS); // [Symbol(num), Symbol(text)]

由枚举的区别可以看出普通枚举方法不能枚举Symbol值作为名称的属性,这也就是说可以通过这样的特性来定义’对内操作的属性’,可以快捷的区分对内操作和对外操作。

6、Symbol元编程

元编程可以简单理解为针对程序本身编程,用来操作目标程序本身的行为特性的编程方式。

7、Symbol元编程的自身静态方法

(1)、Symbol.hasInstance:来实现instanceof关键字底层计算逻辑。
  1. var a = {
  2. [Symbol.hasInstance]: function (data) {
  3. return data instanceof Array
  4. }
  5. }
  6. console.log([1, 2, 3] instanceof a) // true
  7. // 该方式会给a这个对象内部加入一个[Symbol.hasInstance]方法,当调用instanceof方法时,会先走这个[Symbol.hasInstance]方法,起到拦截作用,也可以理解AOP变成模式的一种。

备注:Symbol.hasInstance只适合Object类型,不能自定义Function类型上的Instance指令。

(2)、Symbol.isConcatSpreadable:该属性返回一个boolean值,表示对象使用Array.prototype.concat时是否可以展开
  1. var arr1 = ['c', 'd']
  2. console.log(['a', 'b'].concat(arr1, 'e')); // ["a", "b", "c", "d", "e"]
  3. console.log(arr1[Symbol.isConcatSpreadable]); // undefined
  4. arr1[Symbol.isConcatSpreadable] = false;
  5. console.log(['a', 'b'].concat(arr1, 'e')); // ["a", "b", Array(2), "e"]
  6. arr1[Symbol.isConcatSpreadable] = true;
  7. console.log(['a', 'b'].concat(arr1, 'e')); // ["a", "b", "c", "d", "e"]
  8. console.log(arr1[Symbol.isConcatSpreadable]);
  9. arr1[Symbol.isConcatSpreadable] = undefined;
  10. console.log(['a', 'b'].concat(arr1, 'e')); // ["a", "b", "c", "d", "e"]

当给数组的[Symbol.isConcatSperadable]的值设置为false的时候,当调用concat方法的是,此数组是不能被展开的,当[Symbol.isConcatSperadable]的值设置为true或者undefined时,调用concat方法是可以展开的。

(3)、Symbol.iterator:迭代器,在Array,set,Map,NodeList对象的原型上都有这个迭代器函数,具备迭代器特性的对象可以被for of等迭代方法作用。
  1. var obj = {
  2. 0: 'a',
  3. 1: 'b',
  4. 2: 'c',
  5. length: 3,
  6. [Symbol.iterator]: function() {
  7. let currentIndex = 0;
  8. let next = () => {
  9. return {
  10. value: this[currentIndex],
  11. done: this.length === currentIndex++
  12. }
  13. }
  14. return {
  15. next
  16. }
  17. }
  18. }
  19. for (let p of obj) {
  20. console.log(p) // a, b, c
  21. }

(4)、Symbol.toPrimitive:该方法是对象被转换为原始数据类型时触发。这个参数根据执行的原始值转换类型分别是string、number和boolean
  1. var a = {
  2. [Symbol.toPrimitive](hint) {
  3. if (hint === 'string') {
  4. return 'hello';
  5. }
  6. if (hint === 'number') {
  7. return 10;
  8. }
  9. return true;
  10. }
  11. }
  12. console.log(+a); // 10
  13. console.log(`${a}`); // hello
  14. console.log(a == 1); true

以上总结了四种元编程的能力,还有七种,后续会进行补充。