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,取决于函数的调用方式:
new->this指向正在构造的对象;call、bind、apply->this指向绑定的thisArg;- 如果
thisArg不是对象,会尝试将其转成对象:null/undefined会转成全局对象;primitive type会包装成其对应的reference type
- 如果
- 作为对象的方法调用 ->
this指向该对象;(如果是对象属性引用链,eg.a.b.g(),this指向最近的,这里是b) - 常规函数调用:
- 严格模式 ->
this指向undefined; - 非严格模式 ->
this指向window;
- 严格模式 ->
forEach等,可以传第二个参数,作为第一个参数(回调)中的this;addEventListener中传的回调函数,this指向触发事件的元素;- 在
html中内联作为event处理时使用的this指向监听器所在元素;
new
四步
- 创建新的空的 {};
- 关联该对象的
[[prototype]]到构造函数的prototype属性; this关键字指向这个对象;- 如果构造函数没有 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;
};
};
数据类型
- 基本数据类型(
primitive type):number、boolean、undefined、null、string、symbol、bigint;
- 特点: 大小固定; 值存在栈中; 访问速度快。
- 引用数据类型(
reference type):object、Boolean、Number、String、Array、Function
- 特点: 大小不固定(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. 用作对象的属性名,可以避免命名冲突,但注意:
- 该属性不能用 for 遍历到,可以用
Object.getOwnPropertySymbols(obj)来获得属性名; - 不能使用 . 来调用该属性,必须使用 [] 来访问;
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
-
const num = 123n; BigInt('123456'); // 123456n 10n === 10; // false 10n == 10; // true -
不允许与
number混合操作(TypeError),可转换为同一类型后再操作;但关系运算符不遵循此规则; -
目的是比
number数据类型支持的范围更大的整数值。 -
不支持一元加号(+)运算符, 其他所有运算符都可以用于 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 -
可用于表示高分辨率的时间戳,使用大整数 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?
- 因为要获取
this.[[class]]; - 很多对象继承的
toString方法被重写了,为了能够调用正确的方法,需要使用call/apply的方式;
缺点?
- 需要装箱操作,会产生很多临时对象;
- 无法区分自定义对象(
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. string、array、map、set
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 }
遍历对象属性
for...in:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性);Object.keys(obj):返回数组,包括对象所有自有的 & 可枚举属性(不含 Symbol 属性)的键名;Object.getOwnPropertyNames(obj):返回数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名;Object.getOwnPropertySymbols(obj):返回数组,包含对象自身的所有 Symbol 属性的键名;Reflect.ownKeys(obj):返回数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
上述遍历都遵守同样的属性遍历顺序:
- 首先遍历所有数值键,按照数值升序排列;
- 其次遍历所有字符串键,按照加入时间升序排列;
- 最后遍历所有 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. 箭头函数
- 没有自己的
this。只会从上层作用域继承this(是固定的),call、bind、apply无法改变其this,传给他们的第一个参数会被忽略; - 没有
arguments对象。该关键字是引用外部作用域内的arguments,使用剩余参数是更好的选择; - 不适合
- 用作对象的方法(因为
this没有绑定); - 做事件处理函数;
- 用作对象的方法(因为
- 适合
- 用作
map、filter等不关心this的地方; - 用作需要外层函数
this的内层函数,eg. 对象方法的内层函数;
- 用作
- 不能用作构造函数,其没有
.prototype,也没有super; - 如果函数体只有一句
return,可以省略花括号; - 如果直接 return 字面量对象,要在外面用圆括号包起来,否则会将其误认为是函数体;
- 不可以使用
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"
- 实现并集、交集、和差集
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 区别:
- 没有 clear 方法;
- 没有遍历操作的 api;
- 成员必须是引用类型,否则报错;
- 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 区别:
- 没有 size 属性
- 没有遍历操作的 api;
- WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名;
- 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),就不会再变,任何时候都可以得到这个结果