JavaScript笔记总结

109 阅读19分钟

var \ let \ const 区别?

  • var 是函数作用域;let & const 是块作用域;
  • var 有变量提升(undefined); let & const 有 TDZ,这个区域内用到 声明的变量就会报错 ReferenceError
  • var 可以冗余声明,后面的会覆盖前面的;let & const在相同作用域内不能冗余声明,let& var混合冗余声明也不行;
  • var & let 定义的变量可以重新分配 & 可以不初始化(值为 undefined);const 必须在声明的时候就初始化 & 不能重新赋值;
  • 不用这三个关键字,变量会创建在全局对象上;
  • var在全局作用域声明,非严格模式下会添加到 window 上;
  • let适合 for-i 循环(因为不会泄漏到外部);
  • const 适合 for-of & for-in 循环,不适做迭代变量(因为迭代变量要递增);
  • 使用:能用const就用,其他情况用 let,避免使用var
  • 变量命名:camelCase; 常量命名全大写,下划线做分隔;

this

this 是动态分配,表示函数的执行环境,分为以下几种情况:

1. 在全局上下文中,this 指向全局对象;

2. 函数上下文中的 this,取决于函数的调用方式:

  1. new -> this 指向正在构造的对象;
  2. callbindapply -> this 指向绑定的 thisArg;
    • 如果thisArg不是对象,会尝试将其转成对象:
      1. null/undefined 会转成全局对象;
      2. primitive type 会包装成其对应的reference type
  3. 作为对象的方法调用 -> this 指向该对象;(如果是对象属性引用链,eg. a.b.g()this 指向最近的,这里是 b
  4. 常规函数调用:
    • 严格模式 -> this 指向 undefined;
    • 非严格模式 -> this 指向 window;
  5. forEach 等,可以传第二个参数,作为第一个参数(回调)中的 this;
  6. addEventListener 中传的回调函数,this 指向触发事件的元素;
  7. html 中内联作为 event 处理时使用的 this 指向监听器所在元素;

new

四步

  1. 创建新的空的 {};
  2. 关联该对象的 [[prototype]] 到构造函数的 prototype 属性;
  3. this 关键字指向这个对象;
  4. 如果构造函数没有 return 对象,将自动 return 这个新对象。

注意

箭头函数不能用 new 的方式来调用

手写 new

function myNew(constructor, ...args) {
  if (typeof constructor !== 'function')
    throw new Error('constructor must be a function!');

  const newObj = Object.create(constructor.prototype); // 关联原型
  const result = constructor.apply(newObj, args); // 绑定 this 运行 constructor

  // 如果 constructor 返回的是对象,则返回这个结果,丢弃掉新构建的对象
  if (typeof result === 'object' || typeof result === 'function') return result;

  // 如果 constructor 返回的不是对象,则要 return 这个新对象
  return newObj;
}

call、apply、bind

手写 call

  • 本质是把函数绑定给这个对象,然后通过该对象执行函数,最后删除 对象的这个属性;
  • 没有指定绑定对象时,要给 window;
  • call 接收的是参数列表;
Function.prototype.myCall = function (target = window, ...args) {
  // 为了避免属性名重复
  const symbolKey = Symbol();
  target[symbolKey] = this; // 在函数身上调用 myCall 时,this 指向这个函数

  const res = target[symbolKey](...args); // 调用原函数,记录返回值
  delete target[symbolKey];

  return res;
};

手写 apply

Function.prototype.myApply = function (target = window, args = []) {
  if (!Array.isArray(args)) throw new Error('apply needs array');

  const symbolKey = Symbol();
  target[symbolKey] = this;

  const res = target[symbolKey](...args); // 函数本身接收的是参数列表
  delete target[symbolKey];

  return res;
};

手写 bind

Function.prototype.myBind = function (target = {}, ...outArgs) {
  const symbolKey = Symbol();
  target[symbolKey] = this;

  return function (...innerArgs) {
    const res = target[symbolKey](...outArgs, ...innerArgs); // 函数本身接收的是参数列表
    // delete target[symbolKey];  // delete 之后就不能二次调用了
    return res;
  };
};

数据类型

  1. 基本数据类型(primitive type):numberbooleanundefinednullstringsymbolbigint;
  • 特点: 大小固定; 值存在栈中; 访问速度快。
  1. 引用数据类型(reference type):objectBooleanNumberStringArrayFunction
  • 特点: 大小不固定(eg. 对象可以加属性,数组可以改变长度); 栈中存的是数据在堆中的地址(可以理解为指针,实际数据存在堆中); 访问速度慢。

undefined

用于以下几种情况:

  • 变量声明,但未赋值;
  • 调用函数时,没有传实参的形参;
  • 对象中未赋值的属性,其值为 undefined
  • 函数没有返回值,默认返回 undefined

null

  • 空对象指针,可用于清空变量;
  • 原型链的终点
Object.getPrototypeOf(Object.prototype); // null

symbol

是 es6 新引入的一种原始数据类型,表示独一无二的值

特性

1.Symbol()创建的数据具有唯一性:

即使给的参数相同,创建的数据也不同,可以用来解决命名冲突的问题

Symbol() !== Symbol();

// 没有参数的情况
let name1 = Symbol();
let name2 = Symbol();
name1 === name2; // false
name1 === name2; // false

// 有参数的情况
let name1 = Symbol('flag');
let name2 = Symbol('flag');
name1 === name2; // false
name1 === name2; // false

2. symbol 值不能与其他数据进行运算;

  • 数学计算:不能转换为数字;
  • 字符串拼接:不能隐式转换,可以显示转换;
  • 模板字符串:不能转换
Symbol() + 1; // TypeError
Symbol() + 'a'; // TypeError
let sym = Symbol();
console.log(`${sym}`); // TypeError

Symbol().toString(); // "Symbol()"

3. symbol 定义的对象属性不参与 for-in/of 遍历

只会出现在Reflect.ownKeys(obj)/Object.getOwnPropertySymbols(obj)

let obj = {
  name: 'zhangsan',
  age: 21,
};

let sy = Symbol();
obj[sy] = 'symbol';

console.log(obj); //{name: "zhangsan", age: 21, Symbol(): "symbol"}

for (let key in obj) {
  console.log(key);
} //只输出了name,age

Object.getOwnPropertySymbols(obj); //[Symbol()]
Reflect.ownKeys(obj); //["name", "age", Symbol()]

Object.keys(obj); //["name", "age"]
Object.getOwnPropertyNames(obj); //["name", "age"]
Object.keys(obj); //["name", "age"]
Object.values(obj); //["zhangsan", 21]
JSON.stringify(obj); //{"name":"zhangsan","age":21}

应用

1. 用作对象的属性名,可以避免命名冲突,但注意:

  1. 该属性不能用 for 遍历到,可以用Object.getOwnPropertySymbols(obj)来获得属性名;
  2. 不能使用 . 来调用该属性,必须使用 [] 来访问;

2. 全局共享 symbol

//Symbol.for( ) 参数为创建时传入的描述字符串,该方法可以遍历全局注册表中的的Symbol,当搜索到相同描述,那么会调用这个Symbol,如果没有搜索到,就会创建一个新的Symbol。

let a = Symbol.for('a'); // 在全局注册表中找,没找到,创建描述为 "a" 的 symbol
let b = Symbol.for('a'); // 在全局注册表中找,找到则赋值
a === b; // true

// 注意
let a = Symbol('a'); // 不会在全局注册表中创建
let b = Symbol.for('a');
a === b; // false

//Symbol.keyFor() 通过变量名查询该变量对应的描述是否在 全局注册表中
let a = Symbol('a');
Symbol.keyFor(a); // undefined

let b = Symbol.for('a');
Symbol.keyFor(b); // 'a'

3. 内置 symbol 值

ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法,它们会在特定的场景下自动执行

  • instanceof 对应的是 [Symbol.hasInstance]
  • for-of 对应调用[Symbol.iterator]
  • 在对象上调用toString()时,对应[Symbol.toStringTag]

bigint

  1. const num = 123n;
    BigInt('123456'); // 123456n
    
    10n === 10; // false
    10n == 10; // true
    
  2. 不允许与 number 混合操作(TypeError),可转换为同一类型后再操作;但关系运算符不遵循此规则;

  3. 目的是比 number 数据类型支持的范围更大的整数值。

  4. 不支持一元加号(+)运算符, 其他所有运算符都可以用于 BigInt

    +10n; // TypeError: Cannot convert a BigInt value to a number
    
    10n + 20n; // 30n
    10n - 20n; // -10n
    -10n; // -10n
    10n * 20n; // 200n
    20n / 10n; // 2n
    23n % 10n; // 3n
    10n ** 3n; // 1000n
    const x = 10n;
    ++x; // 11n
    --x; // 9n
    
  5. 可用于表示高分辨率的时间戳,使用大整数 id 等

Boolean

Boolean(); // 可将其他值转换为 boolean

falsy: "", 0, NaN, null, undefined;

truthy: 其他全部

数据类型判断

1. typeof 可以判断基本类型 + "function"

undefined == null; // true

//
typeof null; // "object"
// 因为 typeof 是根据机器码低位标识来判断的,null 的机器码全为 0,
// 而只要机器码低位标识为 000 ,就会被判定为是 "object"

typeof NaN; // "number"
typeof 1; // 'number'
typeof '1'; // 'string'
typeof undefined; // 'undefined'

typeof {}; // 'object'
typeof function () {}; // 'function'

typeof [1, 2]; // "object"
typeof []; // 'object'

2. instanceof 通过原型链判断

检测后者的.prototype 是否出现在前者的原型链上,出现返回 true,否则返回 false;

function Func() {}
const func = new Func();
console.log(func instanceof Func); // true

const obj = {};
const arr = [];
obj instanceof Object; // true
arr instanceof Object; // true
arr instanceof Array; // true

const str = 'abc';
const str2 = new String('abc');
str instanceof String; // false
str2 instanceof String; // true
  • 但要注意后者的.prototype可能被改变;

  • 多窗口之间交互时,可能无法正确判断; 因为多窗口意味着多个全局环境,不同全局环境拥有不同的全局对象,从而拥有不同的内置类型构造函数; 对于数组,可以用如下方式来安全判断:

    // 判断是否是数组
    Array.isArray([1, 2, 3]); // true
    Array.isArray({ foo: 123 }); // false
    Array.isArray('foobar'); // false
    Array.isArray(undefined); // false
    
    Object.prototype.toString.call(myObj) === '[object Array]';
    
  • 只能用于判断复杂数据类型

3. Object.prototype.toString.call(myObj)

  • 其实是找this.[[class]],此属性不能被修改,所以该方法是最准确的方法;
  • 但是注意 [Symbol.toStringTag] 是可以被修改的
  • 适用于所有类型;
Object.prototype.toString.call(new Date()); // "[object Date]"

Object.prototype.toString.call('1'); // "[object String]"
Object.prototype.toString.call(1); // "[object Number]"

Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"

原理

Object.prototype.toString() es 定义

// 以下是根据步骤翻译,为方便理解而写的伪代码。若有不足之处,还望指正。

// 包装对象
function ToObject(O) {
    if (O === undefined || O === null) {
        throw new Error('Throw a TypeError exception')
    }
    let res = null;
    switch(typeof O) {
        case 'Boolean': res = new Boolean(O); break;
        case 'Number': res = new Number(O); break;
        case 'String': res = new String(O); break;
        case 'Symbol': res = new Object(Symbol(O)); break;// 注意这里的 Symbol 不支持 new 语法
        case 'BigInt': res = new Object(BigInt(O)); break;
        default: res = O; break;
    }
    return res;
}

// 判断是不是数组
function IsArray (O) {
    return Array.isArray(O);
}

// toString 内部执行步骤
function toString(O) {
    let bulitinTag = '';
    if (O === undefined || O === null) {
        return `[object ${ O }]`
    }
    O = ToObject(O)
    if (IsArray(O)) {
        bulitinTag = "Array"
    }

    let slotName =  O[[class]]; // 此处为对象的内部插槽class

    switch(slotName) {
        case '[[ParameterMap]]': bulitinTag = "Arguments"; break;
        case '[[Call]]': bulitinTag = "Function"; break;
        case '[[ErrorData]]': bulitinTag = "Error"; break;
        case '[[BooleanData]]': bulitinTag = "Boolean"; break;
        case '[[NumberData]]': bulitinTag = "Number"; break;
        case '[[StringData]]': bulitinTag = "String"; break;
        case '[[DateValue]]': bulitinTag = "Date"; break;
        case '[[RegExpMatcher]]': bulitinTag = "RegExp"; break;
        default:  bulitinTag = "Object"; break;
    }
    let tag = O[Symbol.toStringTag] // 获取对象自定义的[Symbol.toStringTag]方法,有这个就用这个
    if (typeof tag !== 'String') {
        tag = bulitinTag
    }
    return `[object ${ tag }]`
}

为什么用 call?

  1. 因为要获取this.[[class]]
  2. 很多对象继承的toString方法被重写了,为了能够调用正确的方法,需要使用 call / apply 的方式;

缺点?

  1. 需要装箱操作,会产生很多临时对象;
  2. 无法区分自定义对象(instanceof 可以区分)

4. .constructor 获取其构造函数

function A() {}
function B() {}
A.prototype = new B();
console.log(A.constructor === B); // false

var C = new A();
console.log(C.constructor === B); // true
console.log(C.constructor === A); // false

C.constructor = A;
console.log(C.constructor === A); // true
console.log(C.constructor === B); // false

(123).constructor === Number; // true

arr.constructor === Array; // true
  • null & undefined 没有 .constructor;
  • 也是存在该属性被改变的风险;

5. 获取其原型

const arr = [1, 2, 3];

Object.getPrototypeOf(arr) === Array.prototype; // true
arr.__proto__ === Array.prototype; // true
Array.prototype.isPrototypeOf(arr); // true
  • 也是存在该属性被改变的风险;

6. 封装一个用于类型判断的完整工具

function classOf(obj) {
  if (obj === null) return 'null';

  if (typeof obj !== 'object') return typeof obj;
  else
    return Object.prototype.toString.call(obj).slice(8, -1).toLocaleLowerCase();
}

// 测试结果:
classof(2020); // number
classof('石头加油'); // string
classof(true); // boolean
classof(undefined); // undefined
classof(null); // null
classof(Symbol('没毛病!')); // symbol
classof(1n); // bigint
classof({}); // object
classof(classof); // function
classof([]); // array
classof(new Date()); // date
// 还是没法细分自定义类,如果需要的话可以再结合constructor.name继续封装
classof(new classof()); // object

判断数组的方法

/* 1. 检查其原型链上是否有 Array.prototype;
对于跨窗口的数据可能会判断失败 */
[] instanceof Array; //true

/* 2. */
Array.isArray([]);

/* 3. 检查其构造函数
但要注意该属性可能被修改 */
[].constructor === Array;

/* 4. 检查原型 */
Array.prototype.isPrototypeOf([]); // true

/* 5. 返回指定对象的 [[prototype]] */
Object.getPrototypeOf([]) === Array.prototype; // true
[].__proto__ === Array.prototype; // true

/* 6. toString */
Object.prototype.toString.call([]) === '[object Array]';

/* 7. 第三方库 lodash */

闭包

一个函数可以记住它被创建时的外部作用域中的变量(这些变量的值始终保存在内存中)

问题

由于垃圾回收器不会将闭包变量销毁,所以会造成内存泄漏; 而内存泄露可能导致内存溢出;

应用

  • 模仿块级作用域
  • 柯里化
  • 在构造函数中定义特权方法

event loop、宏任务、微任务

DOM

删除子节点

innerHTML= " " remove() removeChild()

ES6 新特性

数组新增

扩展操作符 & 剩余操作符

在赋值操作右侧就是扩展操作符;在赋值操作左侧就是剩余操作符;

扩展操作符 spread operator

适用于所有有 iterator 的对象,eg. stringarraymapset

const arr = [1, 2, 3];
const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];

// 1. 用于给函数传参时,将数组或类数组转为用逗号分隔的参数序列
console.log(...arr);

// 2. 浅拷贝
const copy = [...arr];

// 3. 数组合并
const combined = [...arr1, ...arr2];

// 4. 将类数组结构转换为数组
const arrDiv = [...document.querySelectorAll('div')];

剩余操作符 rest operator

可用于函数传参时将多个参数收集为数组;

// 1. 数组解构、对象解构,注意必须放在最后
const [a, b, ...others] = [1, 2, 3, 4, 5];

// 2. 定义函数时的形参,可以用于收集剩余未指明的参数

Array 构造函数新增方法

Array.from()

对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

console.log(Array.from('foo'));
// Array ["f", "o", "o"]

// 可传第二个参数,相当于 mapFn
console.log(Array.from([1, 2, 3], (x) => x + x));
// Array [2, 4, 6]

Array.of()

创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。

Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 创建一个具有单个元素 7 的数组,而 Array(7) 创建一个长度为 7 的空数组(注意:这是指一个有 7 个空位 (empty) 的数组,而不是由 7 个 undefined 组成的数组)。

// 1. 没有参数,返回一个空数组
Array.of(); // []
Array(); // []

// 2. 只有一个参数
Array.of(7); // [7], 实际上是指定单个元素
Array(7); // (7) [empty × 7], 实际上是指定数组的长度;

// 3. 参数个数 >=2 时,将参数列表转换为数组
Array.of(1, 2, 3); // [1, 2, 3]
Array(1, 2, 3); // [1, 2, 3]

Array.prototype 上新增方法

copyWithin()

浅复制数组的一部分到同一数组中的另一个位置,并返回当前数组; 会修改原数组,但是不会改变原数组的长度。

const array1 = ['a', 'b', 'c', 'd', 'e'];

// copy to index 0 the element at index 3
console.log(array1.copyWithin(0, 3, 4));
// expected output: Array ["d", "b", "c", "d", "e"]

// copy to index 1 all elements from index 3 to the end
console.log(array1.copyWithin(1, 3));
// expected output: Array ["d", "d", "e", "d", "e"]

copyWithin(target);
copyWithin(target, start);
copyWithin(target, start, end);

find() & findIndex()

// find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
const array1 = [5, 12, 8, 130, 44];
const found = array1.find((element) => element > 10); // 12

// findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。
const array1 = [5, 12, 8, 130, 44];
console.log(array1.findIndex((element) => element > 13)); //  3

fill()

使用给定值,填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。

['a', 'b', 'c'].fill(7); // [7, 7, 7]
new Array(3).fill(7); // [7, 7, 7]
['a', 'b', 'c'].fill(7, 1, 2); // ['a', 7, 'c']

// ps: 如果填充的类型是对象,则是浅拷贝

fill(value);
fill(value, start);
fill(value, start, end); // [start, end)

entries(),keys(),values()

// 1. keys() 以可迭代对象形式返回数组的 key
for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

// 2. values() 以可迭代对象形式返回数组的 values
for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

// 3. entries() 以可迭代对象形式返回数组的 entries
for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

includes()

// 1. 用于判断数组是否包含给定的值
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true

// 2. 方法的第二个参数表示搜索的起始位置,默认为0;
// 参数为负数则表示倒数的位置
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true

flat(),flatMap()

// flat()
// 1. 数组扁平化,返回新数组
[1, 2, [3, 4]].flat(); // [1, 2, 3, 4]

// 2. 默认拉平一层,传参数可指定拉平的层数
[1, 2, [3, [4, 5]]].flat(); // [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2); // [1, 2, 3, 4, 5]
// 可以传入Infinity将多层数组转化为一维数组

// flatMap() 首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 连着深度值为 1 的 flat 几乎相同
[2, 3, 4].flatMap((x) => [x, x * 2]);
// [2, 4, 3, 6, 4, 8]
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()

数组的空位

ES6 则是明确将空位转为 undefined,包括 Array.from、扩展运算符、copyWithin()、fill()、entries()、keys()、values()、find()和 findIndex()

对象新增

// 1. 属性名和属性值的变量同名时,可简写
const baz = { foo };
// 等同于
const baz = { foo: foo };

// 2. 对象中方法可简写
const o = {
  method() {
    return 'Hello!';
  },
};
// 等同于
const o = {
  method: function () {
    return 'Hello!';
  },
};

// ps 注意:简写的对象方法不能用作构造函数,否则会报错
const obj = {
  f() {
    this.foo = 'bar';
  },
};

new obj.f(); // TypeError: obj.f is not a constructor

// 3. 属性名表达式,[]
let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world',
};

a['first word']; // "hello"
a[lastWord]; // "world"
a['last word']; // "world"

// 也可以用于方法名
let obj = {
  ['h' + 'ello']() {
    return 'hi';
  },
};

obj.hello(); // hi

// 注意,属性名表达式与简洁表示法,不能同时使用,会报错

// 注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
const keyA = { a: 1 };
const keyB = { b: 2 };

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB',
};

myObject; // Object {[object Object]: "valueB"}

// 4. super关键字,指向当前对象的.__proto__
const proto = {
  foo: 'hello',
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  },
};

Object.setPrototypeOf(obj, proto); // 为obj设置原型对象
obj.find(); // "hello"

// 5. 解构赋值中可用剩余操作符,注意必须是最后一个参数
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }

遍历对象属性

  1. for...in:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性);
  2. Object.keys(obj):返回数组,包括对象所有自有的 & 可枚举属性(不含 Symbol 属性)的键名;
  3. Object.getOwnPropertyNames(obj):返回数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名;
  4. Object.getOwnPropertySymbols(obj):返回数组,包含对象自身的所有 Symbol 属性的键名;
  5. Reflect.ownKeys(obj):返回数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举

上述遍历都遵守同样的属性遍历顺序:

  1. 首先遍历所有数值键,按照数值升序排列;
  2. 其次遍历所有字符串键,按照加入时间升序排列;
  3. 最后遍历所有 Symbol 键,按照加入时间升序排;
Reflect.ownKeys({ [Symbol()]: 0, b: 0, 10: 0, 2: 0, a: 0 });
// ['2', '10', 'b', 'a', Symbol()]

新增静态方法

Object.is()

  • 判断两个值是否为同一个值。
  • 与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0 不等于-0,二是 NaN 等于自身
+0 === -0; //true
NaN === NaN; // false

Object.is(+0, -0); // false
Object.is(NaN, NaN); // true

Object.assign()

  • 用于对象的合并,将源对象 source 的所有可枚举自有属性,复制到目标对象 target,原地修改对象,且返回修改后的对象
  • 注意:是浅拷贝,遇到同名属性会进行替换;
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2); // {a:1, b:2, c:3}
target; // {a:1, b:2, c:3}

Object.getOwnPropertyDescriptors()

获取一个对象的所有自身属性的描述符。

const obj = {
  foo: 123,
  get bar() {
    return 'abc';
  },
};

Object.getOwnPropertyDescriptors(obj);
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: get bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

Object.setPrototypeOf(),Object.getPrototypeOf()

Object.setPrototypeOf() 用来设置一个对象的原型对象; Object.getPrototypeOf() 用于读取一个对象的原型对象

const obj1 = {};
const obj2 = {};
Object.setPrototypeOf(obj1, obj2);
obj1.__proto__ === obj2; // true

Object.getPrototypeOf(obj1) === obj2; // true

Object.keys(),Object.values(),Object.entries()

var obj = { foo: 'bar', baz: 42 };

// 返回数组,包含全部自有的、可枚举属性的键名
Object.keys(obj); // ["foo", "baz"]

// 返回数组,包含全部自有的、可枚举属性的对应值
Object.values(obj); // ["bar", 42]

// 返回数组,包含全部自有的、可枚举属性的键值对
Object.entries(obj); // [ ["foo", "bar"], ["baz", 42] ]

Object.fromEntries()

用于将一个键值对数组转换为对象

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42],
]);
// { foo: "bar", baz: 42 }

函数新增

1. 参数默认值 & 作用域

可以为参数设置默认值,且设置默认值的参数应该是函数的尾参数。否则:会报错,除非用 undefined 作为占位参数传进去

function f(x = 1, y) {
  return [x, y];
}

f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错
f(undefined, 1) // [1, 1]

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域

等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的

下面例子中,y=x 会形成一个单独作用域,x 没有被定义,所以指向全局变量 x

let x = 1;

function f(y = x) {
  // 等同于 let y = x
  let x = 2;
  console.log(y);
}

f(); // 1

2. 函数的 length 属性,将返回没有指定默认值的参数个数

(function (a) {}.length); // 1
(function (a = 5) {}.length); // 0
(function (a, b, c = 5) {}.length); // 2

// 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
(function (a = 0, b, c) {}.length); // 0
(function (a, b = 1, c) {}.length); // 1

// rest 参数也不会计入length属性
(function (...args) {}.length); // 0

3. name 属性,返回该函数的函数名

var f = function () {};
f.name; // "" (ES5)
f.name; // "f" (ES6)

// 如果将一个具名函数赋值给一个变量,则 name属性都返回这个具名函数原本的名字
const bar = function baz() {};
bar.name; // "baz"

// Function 构造函数返回的函数实例,name 属性的值为 anonymous
new Function().name; // "anonymous"

// bind 返回的函数,name 属性值会加上 bound 前缀
function foo() {}
foo.bind({}).name; // "bound foo"

(function () {}.bind({}).name); // "bound "

4. 严格模式

只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错

// 报错
function doSomething(a, b = a) {
  'use strict';
  // code
}

// 报错
const doSomething = function ({a, b}) {
  'use strict';
  // code
};

// 报错
const doSomething = (...a) => {
  'use strict';
  // code
};

const obj = {
  // 报错
  doSomething({a, b}) {
    'use strict';
    // code
  }
};

5. 箭头函数

  1. 没有自己的 this。只会从上层作用域继承 this(是固定的),callbindapply无法改变其this,传给他们的第一个参数会被忽略;
  2. 没有 arguments 对象。该关键字是引用外部作用域内的 arguments,使用剩余参数是更好的选择;
  3. 不适合
    • 用作对象的方法(因为 this 没有绑定);
    • 做事件处理函数;
  4. 适合
    • 用作 mapfilter 等不关心 this 的地方;
    • 用作需要外层函数 this 的内层函数,eg. 对象方法的内层函数;
  5. 不能用作构造函数,其没有.prototype,也没有 super
  6. 如果函数体只有一句 return,可以省略花括号;
  7. 如果直接 return 字面量对象,要在外面用圆括号包起来,否则会将其误认为是函数体;
  8. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数;

set & map

区别?

共同点:集合、字典都可以存储不重复的值 不同点:集合是以[值,值]的形式存储元素,字典是以[键,值]的形式存储

set

Set 集合,其中没有重复的值,可迭代,

1. 基本使用

const s = new Set();

s.add(1); // 添加某个值,返回 Set 结构本身
s.add(1).add(2).add(2); // 2只被添加了一次

s.delete(1); // 删除某个值,返回 boolean,表示删除是否成功
s.has(2); // 返回 boolean,判断该值是否为 Set 的成员
s.clear(); // 清除所有成员,没有返回值

2. 遍历

遍历顺序就是插入顺序

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

3. 有 forEach()

let set = new Set([1, 4, 9]);
set.forEach((value, key, set) => console.log(key + ' : ' + value));
// 1 : 1
// 4 : 4
// 9 : 9

4. 可用于实现数组 / 字符串去重

// 数组
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)]; // [3, 5, 2]

// 字符串
let str = '352255';
let unique = [...new Set(str)].join(''); // "352"
  1. 实现并集、交集、和差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter((x) => b.has(x)));
// set {2, 3}

// (a 相对于 b 的)差集
let difference = new Set([...a].filter((x) => !b.has(x)));
// Set {1}

map

Map 字典,键值对的有序列表,而键和值都可以是任意类型,key 不能重复

1. 基本使用

const m = new Map();

// 添加键值对,然后返回整个 Map 结构,
// 如果key已经有值,则键值会被更新,否则就新生成该键
// 可采用链式写法
map.set('foo', true);
map.set('bar', false);

map.size; // 返回 Map 结构的成员总数

// 读取key对应的键值,如果找不到key,返回undefined
m.get(hello); // Hello ES6!

// 返回 boolean,判断某个键是否在当前 Map 对象之中
m.has('years'); // false

// 删除某个键,返回true。如果删除失败,返回false
m.delete(undefined);

// 清除所有成员,没有返回值
map.clear();

2. 遍历

遍历顺序就是插入顺序

const map = new Map([
  ['F', 'no'],
  ['T', 'yes'],
]);

// 返回键名的遍历器
for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

// 返回键值的遍历器
for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

// 返回键值对的遍历器
for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 同上
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

3. 有 forEach()

map.forEach(function (value, key, map) {
  console.log('Key: %s, Value: %s', key, value);
});

WeakSet

const ws = new WeakSet();

// 可以接受一个具有 Iterable 接口的对象作为参数
const a = [
  [1, 2],
  [3, 4],
];
const ws = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}

与 set 区别:

  1. 没有 clear 方法;
  2. 没有遍历操作的 api;
  3. 成员必须是引用类型,否则报错;
  4. WeakSet 里面的引用只要在外部消失,它在 WeakSet 里面的引用就会自动消失;
let ws = new WeakSet();

// 成员不是引用类型
let weakSet = new WeakSet([2, 3]);
console.log(weakSet); // 报错

// 成员为引用类型
let obj1 = { name: 1 };
let obj2 = { name: 1 };
let ws = new WeakSet([obj1, obj2]);
console.log(ws); //WeakSet {{…}, {…}}

WeakMap

// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap();
const key = { foo: 1 };
wm1.set(key, 2);
wm1.get(key); // 2

// WeakMap 也可以接受一个数组,
// 作为构造函数的参数
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([
  [k1, 'foo'],
  [k2, 'bar'],
]);
wm2.get(k2); // "bar"

与 map 区别:

  1. 没有 size 属性
  2. 没有遍历操作的 api;
  3. WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名;
  4. WeakMap 的键名所指向的对象,一旦不再需要,里面的键名对象和所对应的键值对会自动消失,不用手动删除引用;
const map = new WeakMap();
map.set(1, 2);
// TypeError: 1 is not an object!
map.set(Symbol(), 2);
// TypeError: Invalid value used as weak map key
map.set(null, 2);
// TypeError: Invalid value used as weak map key

应用

举个场景例子:

在网页的 DOM 元素上添加数据,就可以使用 WeakMap 结构,当该 DOM 元素被清除,其所对应的 WeakMap 记录就会自动被移除

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element); // "some information"

注意:WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用

下面代码中,键值 obj 会在 WeakMap 产生新的引用,当你修改 obj 不会影响到内部

const wm = new WeakMap();
let key = {};
let obj = { foo: 1 };

wm.set(key, obj);
obj = null;
wm.get(key);
// Object {foo: 1}

promise

是异步编程的一种解决方案,为了解决回调函数的回调地狱所造成的代码不易阅读、不易于维护的问题

优点

链式操作减低了编码难度 代码可读性明显增强

状态

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

特点:

  • 状态不受外界影响,只有异步操作的结果可以决定是哪一种状态
  • 一旦状态改变(从 pending 变为 fulfilled 和从 pending 变为 rejected),就不会再变,任何时候都可以得到这个结果

generator

proxy

ES6 module

Decorator