[TOC]
es6强制让一个函数有一个参数
if (!a) {
throw Error('cuowu')
}
装饰器
Promise
js中Map和Set区别
Set 对象类似于数组,且成员的值都是唯一的
Map 对象是键值对集合,和 JSON 对象类似,但是 key 不仅可以是字符串还可以是对象
set中存储无序并且不可重复的元素。 map存储的是键值对。key=>VALUE
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
Map:键值对集合,对应于 Object,ES6 中map的key 可以是对象 List:有序可重复的列表,对应于 Array Set:无序且不可重复的列表
const arr = [1, 2, 3, 4, 5, 5, 4, 3, 2, 1]
const set = new Set()
arr.forEach(item => set.add(item))
console.log(set) // 1, 2, 3, 4, 5
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
重复元素在Set中自动被过滤:
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
注意数字3和字符串'3'是不同的元素。
通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
// 数组快速去重
console.log([...new Set(arr)])
var map = new Map()
var obj = { name: '小缘', age: 14 }
map.set(obj, '小缘喵')
map.get(obj) // 小缘喵
map.has(obj) // true
map.delete(obj) // true
map.has(obj) // false
模版字符串
使用${variable}插入变量
var fName = 'Peter', sName = 'Smith', age = 43, job = 'photographer';
var a = 'Hi, I\'m ' + fName + ' ' + sName + ', I\'m ' + age + ' and work as a ' + job + '.';
var b = `Hi, I'm ${ fName } ${ sName }, I'm ${ age } and work as a ${ job }.`;
块级作用域 let const
JavaScript 本身一直是函数式作用域,这就是我们经常将整个 JavaScript 文件封装在一个空的立即调用函数表达式(IIFE)中的原因。这样做是为了隔离文件中的所有变量,因此全局中就不会存在变量名冲突。
ES5 中如果你想限制变量 tmp 的作用范围仅在某一块代码中有效,你不得不使用一个叫 IIFE(Immediately-Invoked Function Expression,立即执行函数表达式) 的模式:
(function () { // IIFE 开始
var tmp = ···;
···
}()); // IIFE 结束
console.log(tmp); // ReferenceError
// ECMAScript 6 中可以简单地使用块和 let 申明(或 const 申明):
{
// 块起始
let tmp = ···;
···
} // 块结束
console.log(tmp); // ReferenceError
var
- 声明提升=> 先上车后买票
- 变量覆盖 =>套牌车
- 没有块级作用域 => 红杏出墙
let
let 声明的变量具有块级作用域,所以可以在不影响外部变量的情况下声明具有相同名称的新局部(内部)变量。 ==同一个块级作用域不能申明同一个变量==
var a = 'car' ;
{
let a = 5;
console.log(a) // 5
let a = 6 // Identifier 'a' has already been declared
}
console.log(a) // car
var和let之间的另一个区别是let不会像var一样被提升。
{
console.log(a); // undefined
console.log(b); // ReferenceError
var a = 'car';
let b = 5;
}
经典面试题
for (var i = 1; i < 5; i++) {
setTimeout(() => { console.log(i); }, 1000);
}
Const
JavaScript 中声明一个常量变量,那么惯例是将变量命名大写。然而,这并不能保证它是一个常量 - 它只是让其他开发人员知道这是一个常量,不应该改变。
- const声明之后必须赋值,否则报错
- 定义的值不能修改,否则报错
- 支持let的其他属性
==const 不会使变量不可变,只是锁定它的赋值。 如果你有一个复杂的赋值(对象或数组),那么该值仍然可以修改。==
{
const c = "tree";
console.log(c); // tree
c = 46; // TypeError!
}
{
const d = [1, 2, 3, 4];
const dave = { name: 'David Jones', age: 32};
d.push(5);
dave.job = "salesman";
console.log(d); // [1, 2, 3, 4, 5]
console.log(dave); // { age: 32, job: "salesman", name: 'David Jones'}
// 上面的方式给const赋值不报错,
直接给const赋值,报错。
dave = {name: 'salesman'} // Assignment to constant variable.
}
块级作用域函数问题
函数声明现在被指定为必须绑定到块级作用域。
{
bar(); // works
function bar() { /* do something */ }
}
bar(); // doesn't work
if ( something) {
function baz() { console.log('I passed') }
} else {
function baz() { console.log('I didn\'t pass') }
}
baz();
在 ES6 之前,这两个函数声明都会被提升,\
结果就是 ‘I didn\’t pass’,不管something 是什么东西。
现在我们会得到 ‘ReferenceError’,因为baz()总是受到块范围的约束。
建议:
- 首选 const。所有不会改变值的变量都可以使用它。
- 其它的使用 let,用于值会被改变的变量。
- 避免使用 var。
扩展运算符
...运算符,它被称为“扩展运算符”。
它有两个主要用途:将数组或对象分散到新的数组或对象中,并将多个参数合并到一个数组中。
... 运算符的另一个特点是它创建一个新的数组或对象。
将变量一起收集到一个数组中。 当你不知道有多少变量传递给函数时,这非常有用。
let a = [3, 4, 5];
let b = [1, 2, ...a, 6];
console.log(b); // [1, 2, 3, 4, 5, 6]
let car = { type: 'vehicle ', wheels: 4};
let fordGt = { make: 'Ford', ...car, model: 'GT'};
console.log(fordGt); // {make: 'Ford', model: 'GT', type: 'vehicle', wheels: 4}
let a = [1, 2, 3];
let b = [ ...a ];
let c = a;
b.push(4);
console.log(a); // [1, 2, 3]
console.log(b); // [1, 2, 3, 4] referencing different arrays
c.push(5);
console.log(a); // [1, 2, 3, 5]
console.log(c); // [1, 2, 3, 5] referencing the same array
function foo(...args) {
console.log(args);
}
foo( 'car', 54, 'tree'); // [ 'car', 54, 'tree' ]
函数默认参数
以使用默认参数定义函数。缺少或未定义的值将使用默认值进行初始化。只要小心 - 因为空值和假值会被强制为0。
默认值可以不仅仅是值 - 它们也可以是表达式或函数。
function foo( a = 5, b = 10) {
console.log( a + b);
}
foo(); // 15
foo( 7, 12 ); // 19
foo( undefined, 8 ); // 13
foo( 8 ); // 18
foo( null ); // 10 as null is coerced to 0
// 默认值为函数
function foo( a ) { return a * 4; }
function bar( x = 2, y = x + 4, z = foo(x)) {
console.log([ x, y, z ]);
}
bar(); // [ 2, 6, 8 ]
bar( 1, 2, 3 ); //[ 1, 2, 3 ]
bar( 10, undefined, 3 ); // [ 10, 14, 3 ]
解构
解构是拆分等号左侧的数组或对象的过程。数组或对象可以来自变量,函数或等式。
有时,你想取值,将它们分配给一个新的变量。 这是通过在等号左边的 key: variable 配对完成的。
对象解构允许的另一件事是为多个变量赋值。
let [ a, b, c ] = [ 6, 2, 9];
console.log(`a=${a}, b=${b}, c=${c}`); //a=6, b=2, c=9
function foo() { return ['car', 'dog', 6 ]; }
let [ x, y, z ] = foo();
console.log(`x=${x}, y=${y}, z=${z}`); // x=car, y=dog, z=6
function baz() {
return {
x: 'car',
y: 'London',
z: { name: 'John', age: 21}
};
}
let { x: vehicle, y: city, z: { name: driver } } = baz();
console.log(
`I'm going to ${city} with ${driver} in their ${vehicle}.`
); // I'm going to London with John in their car.
let { x: first, x: second } = { x: 4 };
console.log( first, second ); // 4, 4
对象字面量和简明参数
当您从变量创建对象字面量时,ES6 允许您在与 key 与变量名称相同的情况下省略 key 名。
let a = 4, b = 7;
let c = { a: a, b: b };
let concise = { a, b };
console.log(c, concise) // {a: 4, b: 7}, {a: 4, b: 7}
动态属性名称
使用动态分配的 key 创建或添加属性的功能。
let city= 'sheffield_';
let a = {
[ city + 'population' ]: 350000
};
a[ city + 'county' ] = 'South Yorkshire';
console.log(a); // {sheffield_population: 350000, sheffield_county: 'South Yorkshire' }
箭头函数
箭头函数有两个主要方面:结构和this绑定。
不需要功能关键字,并且它们自动返回箭头之后的任何内容。
如果函数需要的不仅仅是一个简单的计算,可以使用大括号,并且该函数会返回花括号块范围返回的任何内容。
对于箭头函数最有用的地方之一是在map(),forEach()或sort()之类的数组函数中。
var foo = function( a, b ) {
return a * b;
}
let bar = ( a, b ) => a * b;
数字字面量
ES5 代码很好地处理了十进制和十六进制数字格式,但未指定八进制格式。事实上,它在严格的模式下被禁止。
ES6添加了一种新格式,在最初的 0 之后添加一个 o (注意是字母)以将该数字声明为八进制数。ES6 还添加了二进制格式。
Number( 29 ) // 29
Number( 035 ) // 35 in old octal form.
Number( 0o35 ) // 29 in new octal form
Number( 0x1d ) // 29 in hexadecimal
Number( 0b11101 ) // 29 in binary form
class继承和function继承的区别
class Super {}
class Sub extends Super {}
const sub = new Sub();
Sub.__proto__ === Super;
子类可以直接通过 proto 寻址到父类。
function Super() {}
function Sub() {}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
var sub = new Sub();
Sub.__proto__ === Function.prototype;
而通过 ES5 的方式,Sub.__proto__ === Function.prototype
静态方法(Static Methods)
在类中,我们可以使用static关键字来声明静态方法。类的实例无法访问静态方法,因为声明静态方法的类属于类对象(class object)。
class Repo{
static getName() {
return "Repo name is modern-js-cheatsheet"
}
}
// Note that we did not have to create an instance of the Repo class
console.log(Repo.getName()) // Repo name is modern-js-cheatsheet
let r = new Repo();
console.log(r.getName()) // Uncaught TypeError: repo.getName is not a function
通过使用this关键字,静态方法可以调用另一个静态方法,但这不适用于非静态方法(non-static methods)。非静态方法不能直接使用this关键词访问静态方法。
静态方法调用另一个静态方法
要想静态方法调用另一个静态方法,可以这样使用this关键字;
class Repo{
static getName() {
return "Repo name is modern-js-cheatsheet"
}
static modifyName(){
return this.getName() + '-added-this'
}
}
console.log(Repo.modifyName()) // Repo name is modern-js-cheatsheet-added-this
非静态方法调用静态方法
非静态方法可以通过这两种方式调用静态方法;
- 使用类名称。
为了在非静态方法里访问静态方法,我们使用类名称(class name)并像属性一样调用静态方法。例如ClassName.StaticMethodName
class Repo{
static getName() {
return "Repo name is modern-js-cheatsheet"
}
useName(){
return Repo.getName() + ' and it contains some really important stuff'
}
}
// we need to instantiate the class to use non-static methods
let r = new Repo()
console.log(r.useName()) // Repo name is modern-js-cheatsheet and it contains some really important stuff
- 使用构造函数
静态方法可以作为构造函数对象(constructor object)的属性来调用。
class Repo{
static getName() {
return "Repo name is modern-js-cheatsheet"
}
useName(){
// Calls the static method as a property of the constructor
return this.constructor.getName() + ' and it contains some really important stuff'
}
}
// we need to instantiate the class to use non-static methods
let r = new Repo()
console.log(r.useName()) // Repo name is modern-js-cheatsheet and it contains some really important stuff
新对象替换老对象
state.obj = {...state.obj, newProp: 123}
oldObj = Object.assign({}, newObj)
oldObj = JSON.parse(JSON.stringify(newObj))
在对象上添加新属性
Vue.set(obj, 'newProp', 123)
let
var a = [];
for(let i=0;i<10;i++){
a[i] = function(){
console.log(i);
}
}
a[6]();
不存在变量提升
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
不允许重复声明 不能在函数内部重新声明参数
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
块级作用域的不合理场景
- 内层变量可能会覆盖外层变量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
- 用来计数的循环变量泄露为全局变量 循环结束后,i并没有消失,泄露成了全局变量。
块级作用域: 可以任意嵌套 块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
}
// 函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
// 不报错
'use strict';
if (true) {
function f() {}
}
// 报错
'use strict';
if (true)
function f() {}
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
-
改变常量的值会报错。
const PI = 3.1415; PI // 3.1415
PI = 3; // TypeError: Assignment to constant variable.
-
只声明不赋值,报错
const foo; // SyntaxError: Missing initializer in const declaration
-
作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) { const MAX = 5; }
MAX // Uncaught ReferenceError: MAX is not defined
-
声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
if (true) { console.log(MAX); // ReferenceError const MAX = 5; }
-
声明的常量,也与let一样不可重复声明。
let age = 25; var message = "Hello!";
// 以下两行都会报错 const message = "Goodbye!"; const age = 30;
本质
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
ES6 声明变量的六种方法 var命令和function命令,let和const命令,import命令和class命令。
顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩。