map和Object的区别

130 阅读16分钟

new操作符的实现原理

new操作符是用来创建对象的,它的实现原理如下:

(1)首先创建了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

  function myNew(fun,...args){
            // let obj=Object.create(fun.prototype)第一个参数是新创建对象的原型对象。
            let obj={}
            obj.__proto__=fun.prototype

            let res=fun.call(obj,...args)
            if(res &&(typeof res ==="object" || typeof res ==="function")){
                return res
            }
            return obj
        }

        function Person(name,age){
            this.name = name;
            this.age = age;
        }

        console.log(myNew(Person,'张三',20));

map和Object的区别

mapobject
键可以是任意类型,包括原始类型和对象引用键只能是字符串或者 Symbol 类型
键值对有序键值对无序
方法:size 属性用于获取元素个数forEach() 方法用于遍历元素for in 循环
适合于需要存储大量动态数据的情况适合于保存固定且已知的属性

Map使用

在 JavaScript 中,Map 是一种用来存储键值对的数据结构,你可以使用以下方法来使用 Map

  1. 创建一个新的 Map 对象:

    const myMap = new Map();
    
  2. 添加键值对到 Map 中:

    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    
  3. Map 中获取值:

    console.log(myMap.get('key1')); // 输出: value1
    
  4. 检查 Map 中是否包含某个键:

    console.log(myMap.has('key1')); // 输出: true
    
  5. 删除 Map 中的键值对:

    myMap.delete('key2');
    
  6. 获取 Map 的大小(包含的键值对数量):

    console.log(myMap.size); // 输出: 1
    
  7. 遍历 Map 中的键值对:

    myMap.forEach((value, key) => {
      console.log(key + ' = ' + value);
    });
    

Map 还有许多其他的方法和特性,可以根据你的具体需求来使用。需要注意的是,Map 的键可以是任意类型,包括对象、数字、字符串等。

Object使用

在 JavaScript 中,Object 是一种非常常用的数据结构,用于存储键值对。以下是一些使用 Object 的常见方法:

  1. 创建一个新的空对象:

    const myObject = {};
    
  2. 添加属性到对象中:

    myObject.key1 = 'value1';
    myObject['key2'] = 'value2';
    
  3. 从对象中获取属性的值:

    console.log(myObject.key1); // 输出: value1
    console.log(myObject['key2']); // 输出: value2
    
  4. 检查对象中是否包含某个属性:

    console.log('key1' in myObject); // 输出: true
    
  5. 删除对象中的属性:

    delete myObject.key2;
    
  6. 获取对象的所有属性名:

    const keys = Object.keys(myObject);
    console.log(keys); // 输出: ['key1']
    
  7. 获取对象的所有属性值:

    const values = Object.values(myObject);
    console.log(values); // 输出: ['value1']
    

Object 还有许多其他的方法和特性,比如Object.entries() 用于获取对象的键值对数组,Object.assign() 用于对象的合并等。需要根据具体的需求来选择使用哪种方法。

map和weakMap的区别

MapweakMap
键是强引用类型,只要键存在于 Map 中,它所引用的对象就不会被垃圾回收键是弱引用类型,键引用的对象如果没有其他引用存在,会被垃圾回收
键可以是任意类型,包括原始类型和对象引用对象
Map 提供了迭代方法 (例如 keys()values()entries()),可以直接迭代 Map 的键值对WeakMap 由于键是弱引用,没有提供直接访问键的迭代方法。
Map 提供了 size 属性,可以获取其存储的键值对数量

由于这些区别,Map 更适用于存储需要长期保存的键值对,而 WeakMap 更适用于存储临时的、不需要长期保存并且键的生命周期由其他的对象决定的键值对。因为 WeakMap 的键是弱引用,所以无法遍历键、无法获取大小,也无法清空整个 WeakMap

ES6模块与CommonJS模块有什么异同?

ES6模块和CommonJS模块是两种不同的模块系统,它们在很多方面有异同:

异同点:

  1. 语法差异:

    • ES6模块使用importexport关键字来导入和导出模块,

    • CommonJS模块使用require()module.exports来导入和导出模块

  2. 导入导出方式:

    • ES6模块支持静态导入,模块的导入在代码静态分析阶段就能确定。
    • CommonJS模块的导入是动态的,导入语句的执行发生在运行时。
  3. 模块加载时机:

    • ES6模块是在编译时加载,因此模块的依赖关系在代码静态分析阶段就能确定。
    • CommonJS模块是在运行时加载,模块的依赖关系只能在运行时确定。
  4. 默认导出和命名导出:

    • ES6模块可以同时支持默认导出和命名导出。
    • CommonJS模块不直接支持默认导出,但可以通过module.exports来实现类似默认导出的功能。
  5. 浏览器兼容性:

    • ES6模块是原生支持的,可以直接在支持ES6模块的现代浏览器中使用。
    • CommonJS模块是Node.js环境中使用的模块系统,需要借助工具(如webpack或者Browserify)才能在浏览器中使用。

object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别

  • Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。

  • 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。

Object.assign和扩展运算符都是浅拷贝。

浅拷贝是指只拷贝对象的第一层属性,对于对象的内部嵌套对象或数组,则会共享引用,而不是创建新的引用。

示例代码:

// 浅拷贝示例
const obj1 = { a: 1, b: { c: 2 } };

// 使用Object.assign进行浅拷贝
const obj2 = Object.assign({}, obj1);
obj2.b.c = 3;

console.log(obj1); // 输出: { a: 1, b: { c: 3 } }

// 使用扩展运算符进行浅拷贝
const obj3 = { ...obj1 };
obj3.b.c = 4;

console.log(obj1); // 输出: { a: 1, b: { c: 4 } }

可以看到,浅拷贝只会复制对象的第一层属性,对于嵌套的对象或数组,仍然是共享同一个引用。因此,修改拷贝后的对象的嵌套属性会影响原始对象。

defienProperty与proxy有何作用,区别是什么?

Object.defineProperty和Proxy都是用来实现对对象的监视和控制的机制

Object.defineProperty的作用和区别:

  • 添加或删除对象的属性时,Vue 检测不到。因为添加或删除的对象没有在初始化进行响应式处理,只能通过$set 来调用Object.defineProperty()处理。
  • 无法监控到数组下标和长度的变化。
  • 只能对已有的属性进行操作,无法监视对象的整体操作,比如属性的读取、删除等行为。

示例:

const obj = { name: 'Alice' };
Object.defineProperty(obj, 'name', { writable: false }); // 将name属性设为不可写

Proxy的作用和区别:

  • 可以监控整个对象的操作。
  • 通过Proxy可以代理整个对象,可以监听同级结构下的所有属性变化,并在代理过程中定义对应的操作方法(如get、set、deleteProperty等),从而实现对对象的行为进行拦截和自定义处理。
  • Proxy 可以监听数组的变化

示例:

const handler = {
  get: function(target, prop) {
    console.log(`Reading property ${prop}`);
    return target[prop];
  }
};
const proxy = new Proxy({ name: 'Alice' }, handler);
console.log(proxy.name); // 会触发get拦截器,打印"Reading property name"并返回"Alice"

扩展运算符的作用及使用场景

可以在某些情况下方便地将一个数组或对象展开成多个独立的值,或将多个值组合成一个数组或对象

(1)对象扩展运算符

对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

(2)数组扩展运算符

数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。

  1. 合并数组:扩展运算符可以将一个数组中的元素展开,并插入到另一个数组中,实现数组的合并。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const mergedArr = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
  1. 复制数组:可以使用扩展运算符来复制一个数组。
const originalArr = [1, 2, 3];
const copyArr = [...originalArr]; // 复制了originalArr数组
  1. 传递函数参数:在调用函数时,可以使用扩展运算符传递数组元素作为函数的参数。
function sum(x, y, z) {
  return x + y + z;
}
const numbers = [1, 2, 3];
const result = sum(...numbers); // result为6
  1. 合并对象:可以使用扩展运算符快速合并对象的属性。
const obj1 = { foo: 'bar' };
const obj2 = { baz: 'qux' };
const mergedObj = { ...obj1, ...obj2 }; // { foo: 'bar', baz: 'qux' }
  1. 字符串处理:可以将字符串分解为字符数组。
const str = 'hello';
const chars = [...str]; // ['h', 'e', 'l', 'l', 'o']

11.14

1.遍历器(Iterator)

  • 它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

  • Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

  • Iterator 的遍历过程是这样的。

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

2.for...in 和for... of区别

  • for…in 获取的是对象的键名,for…of 遍历获取的是对象的键值;

  • for… in 会遍历对象的整个原型链,而 for … of 只遍历当前对象;

  • for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;

  • 总结: for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。

Generator

  • 是Es6提供的一种异步编程解决方案,就是一个函数

  • 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

  • 形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
  • 总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

  • next传入的参数, 第二次传入为第一次yield返回结果

Set

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4

Set 实例的属性和方法

Set 结构的实例有以下属性。

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。

Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。

遍历操作

Set 结构的实例有四个遍历方法,可以用于遍历成员。

  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员

Map

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键

原因

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

实例的属性和操作方法

Map 结构的实例有以下属性和操作方法。

(1)size 属性

size属性返回 Map 结构的成员总数。

const map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2

(2)Map.prototype.set(key, value)

set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

const m = new Map();

m.set('edition', 6)        // 键是字符串
m.set(262, 'standard')     // 键是数值
m.set(undefined, 'nah')    // 键是 undefined

set方法返回的是当前的Map对象,因此可以采用链式写法。

let map = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

(3)Map.prototype.get(key)

get方法读取key对应的键值,如果找不到key,返回undefined

const m = new Map();

const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数

m.get(hello)  // Hello ES6!

(4)Map.prototype.has(key)

has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

const m = new Map();

m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');

m.has('edition')     // true
m.has('years')       // false
m.has(262)           // true
m.has(undefined)     // true

(5)Map.prototype.delete(key)

delete()方法删除某个键,返回true。如果删除失败,返回false

const m = new Map();
m.set(undefined, 'nah');
m.has(undefined)     // true

m.delete(undefined)
m.has(undefined)       // false

(6)Map.prototype.clear()

clear()方法清除所有成员,没有返回值。

let map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2
map.clear()
map.size // 0

遍历方法 

Map 结构原生提供三个遍历器生成函数和一个遍历方法。

  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。

Super的作用

在面向对象编程中,特别是在使用 JavaScript 中的类时,关键字 super 扮演着重要的角色。super 关键字主要用于两个方面:

  1. 在子类的构造函数中调用父类的构造函数:在子类的构造函数中,我们使用 super 来调用父类的构造函数,实现对父类属性的初始化。这是 super 的最常见用法。示例如下:
class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类的构造函数
    this.breed = breed;
  }
}

在这个例子中,Dog 类继承自 Animal 类,并在其构造函数中通过 super 调用了父类 Animal 的构造函数,传递了 name 参数进行初始化。

  1. 在子类方法中调用父类的同名方法:当子类和父类具有同名的方法时,子类中的方法可以通过 super 调用父类的同名方法,实现对父类方法的扩展或覆写。示例如下:
class Animal {
  makeSound() {
    console.log("Generic animal sound");
  }
}

class Dog extends Animal {
  makeSound() {
    super.makeSound(); // 调用父类的方法
    console.log("Bark");
  }
}

在这个例子中,Dog 类覆写了父类 AnimalmakeSound 方法,在方法内部使用 super.makeSound() 调用了父类的同名方法,并在其后添加了狗的特有声音。

总之,super 关键字在类继承中起着连接子类和父类的重要作用,它使得子类可以轻松地调用父类的构造函数或者同名方法,实现了类的继承和方法的覆写,使得代码更加灵活和可维护。

11.15

1.async 和defer 的区别

defer 和 async属性都是去异步加载外部的JS脚本文件,它们都不会阻塞页面的解析

  • 执行顺序:

多个带async属性的标签,不能保证加载的顺序;

多个带defer属性的标签,按照加载顺序执行;

  • 脚本是否并行执行:

async 属性,表示后续文档的加载和执行与js脚本的加载和执行是并行进行的

defer 属性,后续文档的加载和js脚本的加载是并行的(仅是加载过程),js脚本需要等到文档解析完成之后才执行

2.Object.defineProperty

Object.defineProperty() 是 JavaScript 中的一个内置方法,用于定义或修改对象的属性。

该方法有三个参数:

  • 对象:要定义或修改属性的对象。
  • 属性名称:字符串类型,表示要定义或修改的属性的名称。
  • 属性描述符:一个对象,用来描述要定义或修改的属性的特性。

属性描述符对象可以包含以下属性:

  • configurable:用于指定属性是否可以被删除或修改特性,默认为 false。
  • enumerable:用于指定属性是否可以被枚举,默认为 false。
  • value:用于指定属性的值,默认为 undefined。
  • writable:用于指定属性的值是否可以被修改,默认为 false。
  • get:一个函数,用于获取属性的值。
  • set:一个函数,用于设置属性的值。

使用 Object.defineProperty() 方法,可以在创建对象时或者在对象创建后动态地定义和修改属性。该方法通过属性描述符对象提供了灵活的属性定义和修改功能,可以控制属性的特性(如可写性、可枚举性等)以及属性的访问和赋值行为。

3.Proxy

Proxy 是 JavaScript 中的一个内置对象,用于创建代理对象,可以理解为在对象之前设置一个“拦截”,当该对象被访问的时候,都必须经过这层拦截。意味着你可以在这层拦截中进行各种操作。比如你可以在这层拦截中对原对象进行处理,返回你想返回的数据结构。 Proxy 的基本语法如下:

const proxy = new Proxy(target, handler);
  • target:表示被代理的目标对象。
  • handler:一个对象,用于定义拦截目标对象操作的各种行为。

handler 可以包含多个拦截操作的方法,例如:

  • get:拦截对目标对象属性的访问。
  • set:拦截对目标对象属性的设置。
  • apply:拦截对目标对象的函数调用。
  • deleteProperty:拦截对目标对象属性的删除操作。
  • has:拦截对目标对象 in 操作符的判断。

4.Reflect

Reflect 是 JavaScript 的内置对象之一,它提供了一组静态方法,用于操作对象。它的设计目标是提供一种统一、更易于使用的方式来执行对象操作,以替代一些传统的操作符或函数。

5.Promise

  • Promise 是 JavaScript 中用于处理异步操作的对象,它提供了一种更加结构化,更易于管理异步操作的方式
  • 状态:Promise 对象最初处于未决状态(Pending),随后可能变为已完成状态(Fulfilled)或已拒绝状态(Rejected)
  • 处理程序:通过使用 Promise 的 then 方法,可以指定异步操作完成时要执行的操作;也可以使用 catch 方法来捕获异步操作失败时的错误。
  • 链式调用:Promise 实例可以通过链式调用 then 方法,使得多个异步操作按顺序执行,并且更加清晰和易于管理。