文章目录
1. ECMASript 相关介绍
ECMA(European Computer Manufacturers Association)中文名称为欧洲计算机制造商协会,这个组织的目标是评估、开发和认可电信和计算机标准。1994 年后该组织改名为Ecma 国际。
ECMAScript 是由Ecma 国际通过ECMA-262 标准化的脚本程序设计语言。
ECMA-262 历史
注:从ES6 开始,每年发布一个版本,版本号比年份最后一位大1
http://kangax.github.io/compat-table/es6/ 可查看兼容性
为什么要学习ES6
- ES6 的版本变动内容最多,具有里程碑意义
- ES6 加入许多新的语法特性,编程实现更简单、高效
- ES6 是前端发展趋势,就业必备技能
2. let 和 const 关键字
ES2015 引入了两个重要的 JavaScript 新关键词:let 和 const。
这两个关键字在 JavaScript 中提供了块作用域(Block Scope)变量(和常量)。
在 ES2015 之前,JavaScript 只有两种类型的作用域:全局作用域 和函数作用域。
1.全局(在函数之外)声明的变量拥有全局作用域,全局变量可以在 JavaScript 程序中的任何位置访问。
var carName = "porsche";
// 此处的代码可以使用 carName
function myFunction() {
// 此处的代码也可以使用 carName
}
2.局部(函数内)声明的变量拥有函数作用域。局部变量只能在它们被声明的函数内访问。
// 此处的代码不可以使用 carName
function myFunction() {
var carName = "porsche";
// code here CAN use carName
}
// 此处的代码不可以使用 carName
2.1 let 关键字
let 关键字用来声明变量,使用let 声明的变量有几个特点:
1) 不允许重复声明
var x = 10;// 现在,x 为 10
var x = 6;// 现在,x 为 6
//在相同的作用域,或在相同的块中,通过 let 重新声明一个 var 变量是不允许的:
var x = 10; // 允许
let x = 6; // 不允许
{
var x = 10; // 允许
let x = 6; // 不允许
}
//在相同的作用域,或在相同的块中,通过 let 重新声明一个 let 变量是不允许的:
let x = 10; // 允许
let x = 6; // 不允许
{
let x = 10; // 允许
let x = 6; // 不允许
}
//在不同的作用域或块中,通过 let 重新声明变量是允许的:
let x = 6; // 允许
{
let x = 7; // 允许
}
{
let x = 8; // 允许
}
2) 块儿级作用域
通过 var 关键词声明的变量没有块作用域。在块 {} 内声明的变量可以从块之外进行访问。可以使用 let 关键词声明拥有块作用域的变量。在块 {} 内声明的变量无法从块外访问:
{
var x = 10;
}
// 此处可以使用 x
{
let x = 10;
}
// 此处不可以使用 x
使用 var 关键字重新声明变量会带来问题。在块中重新声明变量也将重新声明块外的变量:
var x = 10;
// 此处 x 为 10
{
var x = 6; //全局变量
// 此处 x 为 6
}
// 此处 x 为 6
使用 let 关键字重新声明变量可以解决这个问题。在块中重新声明变量不会重新声明块外的变量:
var x = 10;
// 此处 x 为 10
{
let x = 6;
// 此处 x 为 6
}
// 此处 x 为 10
防止循环变量变成全局变量
var i = 7;
for (var i = 0; i < 10; i++) {
// 一些语句
}
// 此处,i 为 10,在循环中使用的变量使用 var 重新声明了循环之外的变量
let i = 7;
for (let i = 0; i < 10; i++) {
// 一些语句
}
// 此处 i 为 7
3) 不存在变量提升
console.log(a); // a is not defined
let a = 20;
4) 暂时性死区
var tmp = 123;
if (true) {
console.log(tmp); //tmp is not defined
let tmp = 'abc';
}
经典面试题
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0](); //2
arr[1](); //2
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0](); //0
arr[1](); //1
采用 var 声明的变量 i 是全局的,函数执行时输出的都是全局作用域下的i值。
采用 let 声明的变量 i 在每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值.
2.2 const 关键字
const 关键字作用:声明常量,常量就是值(内存地址)不能变化的量。const 声明有以下特点:
1) 不允许重复声明
2) 块儿级作用域
if (true) {
const a = 10;
}
console.log(a) // a is not defined
3) 不存在变量提升
4) 声明必须赋初始值
const PI; // Missing initializer in const declaration
const PI = 3.14;
5) 值不允许修改
const PI = 3.14;
PI = 100; // Assignment to constant variable.
6) 标识符一般为大写
7) 不是真正的常数
const 的本质: const 没有定义常量值。它定义了对值的常量引用。因此,我们不能更改常量原始值,但我们可以更改常量对象的属性。
//如果我们将一个原始值赋给常量,我们就不能改变原始值:
const PI = 3.141592653589793;
PI = 3.14; // 会出错
/* -------可以更改常量对象和数组的属性,但是无法重新为常量对象和数组赋值--------- */
// 创建常量对象
const car = {type:"Fiat", model:"500", color:"white"};
// 修改属性:
car.color = "red";
// 添加属性
car.owner = "Johnson";
//但是我们不能对常量对象重新赋值:
const car = {type:"Fiat", model:"500", color:"white"};
car = {type:"Volvo", model:"EX60", color:"red"}; // 错误
// 创建常量数组
const cars = ["Saab", "Volvo", "BMW"];
// 修改元素
cars[0] = "Toyota";
// 添加元素
cars.push("Audi");
//但是我们不能对常量数组重新赋值:
const cars = ["Saab", "Volvo", "BMW"];
cars = ["Toyota", "Volvo", "Audi"]; // 错误
注意: 对象属性修改和数组元素变化不会出发const 错误
应用场景:声明对象类型使用const,非对象类型声明选择let
3. 解构赋值
解构赋值语法是一种 Javascript 表达式。通过解构赋值, 可以将属性/值从对象/数组中取出, 赋值给其他变量。
3.1 数组解构
声明变量并赋值时的解构
var foo = ["one", "two", "three"];
var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
var x = [1, 2, 3, 4, 5];
var [y, z] = x;
console.log(y); // 1
console.log(z); // 2
变量先声明后赋值
var a, b;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
交换变量
var a = 1;
var b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
解析从一个函数返回的数组
function f() {
return [1, 2];
}
var a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2
忽略某些返回值
function f() {
return [1, 2, 3];
}
var [a, , b] = f();
console.log(a); // 1
console.log(b); // 3
将剩余参数赋值给一个变量
var [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]
注意:如果剩余元素右侧有逗号,会抛出 SyntaxError
,因为剩余元素必须是数组的最后一个元素。
var [a, ...b,] = [1, 2, 3];
// SyntaxError: rest element may not have a trailing comma
3.2 解构对象
基本赋值
var o = {p: 42, q: true};
var {p, q} = o;
console.log(p); // 42
console.log(q); // true
给新的变量名赋值:可以从一个对象中提取变量并赋值给和对象属性名不同的新的变量名。
var o = {p: 42, q: true};
var {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true
默认值:变量可以先赋予默认值。当要提取的对象对应属性解析为 undefined,变量就被赋予默认值。
var {a = 10, b = 5} = {a: 3};
console.log(a); // 3
console.log(b); // 5
对象解构中的rest
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}
a; // 10
b; // 20
rest; // { c: 30, d: 40 }
解构对象时会查找原型链 (如果属性不在对象自身,将从原型链中查找)
// 声明对象 和 自身 self 属性
var obj = {self: '123'};
// 在原型链中定义一个属性 id
obj.__proto__.id = '456';
// test
const {self, id} = obj;
// self "123"
// id "456"(访问到了原型链)
注意:频繁使用对象方法、数组元素,就可以使用解构赋值形式
4. 模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识,在模版字符串内使用反引号(`)时,需要在它前面加转义符(\)。特点:
-
字符串中可以出现换行符
-
可以使用${xxx} 形式输出变量
let html = ` <div>
<span>${result.name}</span>
<span>${result.age}</span>
<span>${result.sex}</span>
</div> `;
多行字符串
//使用普通字符串,你可以通过以下的方式获得多行字符串:
console.log('string text line 1\n' +
'string text line 2');
// "string text line 1
// string text line 2"
//要获得同样效果的多行字符串,只需使用如下代码:
console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"
插入表达式
//在普通字符串中嵌入表达式,必须使用如下语法:
var a = 5;
var b = 10;
console.log('Fifteen is ' + (a + b) + ' and\nnot ' + (2 * a + b) + '.');
// "Fifteen is 15 and
// not 20."
//现在通过模板字符串,我们可以使用一种更优雅的方式来表示:
var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."
注意:当遇到字符串与变量拼接的情况使用模板字符串
在模板字符串中可以调用函数
const sayHello = function () {
return '追不到我吧 我就是这么强大';
};
let greet = `${sayHello()} 哈哈哈哈`;
console.log(greet); // 追不到我吧 我就是这么强大 哈哈哈哈
5. 简化对象写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁
let username='liming';
let age=39;
//以前的写法
// let obj={
// username:username,
// age:age
// };
//简化后的的写法
let obj={
username,//同名的属性可以简略不写
age,
getName(){//可以省略函数的function
return this.username;
}
};
console.log(obj);
注意:对象简写形式简化了代码,所以以后用简写就对了
6. 箭头函数
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this
,arguments
,super
或new.target
。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
() => {}
const fn = () => {}
const materials = [
'Hydrogen',
'Helium',
'Lithium',
'Beryllium'
];
console.log(materials.map(material => material.length));
// expected output: Array [8, 6, 7, 9]
箭头函数的注意点:
- 如果形参只有一个,则小括号可以省略
function fn (v) {
return v;
}
const fn = v => v;
- 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
function sum(num1, num2) {
return num1 + num2;
}
const sum = (num1, num2) => num1 + num2;
3) 箭头函数不绑定this关键字, 它只会从自己的作用域链的上一层继承this。
function fn () {
console.log(this); //obj
return () => {
console.log(this) //obj
}
}
const obj = {name: 'zhangsan'};
const resFn = fn.call(obj);
resFn();
在下面的代码中,传递给setInterval
的函数内的this
与封闭函数中的this
值相同:
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // |this| 正确地指向 p 实例
}, 1000);
}
var p = new Person();
由于 箭头函数没有自己的this指针,通过 call()
或 apply()
方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。(这种现象对于bind方法同样成立)
var adder = {
base : 1,
add : function(a) {
var f = v => v + this.base;
return f(a);
},
addThruCall: function(a) {
var f = v => v + this.base;
var b = {
base : 2
};
return f.call(b, a);//b参数被忽略
}
};
console.log(adder.add(1)); // 输出 2
console.log(adder.addThruCall(1)); // 仍然输出 2
4) 箭头函数不能作为构造函数实例化
箭头函数不能用作构造器,和 new
一起用会抛出错误。
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
箭头函数没有prototype
属性。
var Foo = () => {};
console.log(Foo.prototype); // undefined
5) 不能使用arguments
箭头函数不绑定Arguments 对象。因此,在本示例中,arguments
只是引用了封闭作用域内的arguments:
var arguments = [1, 2, 3];
var arr = () => arguments[0];
arr(); // 1
function foo(n) {
var f = () => arguments[0] + n; // 隐式绑定 foo 函数的 arguments 对象. arguments[0] 是 n, 即传给foo函数的第一个参数
return f();
}
foo(1); // 2
foo(2); // 4
foo(3,2);//6
在大多数情况下,使用剩余参数是相较使用arguments
对象的更好选择。
function foo(arg) {
var f = (...args) => args[0];
return f(arg);
}
foo(1); // 1
function foo(arg1,arg2) {
var f = (...args) => args[1];
return f(arg1,arg2);
}
foo(1,2); //2
7. 剩余参数(rest 参数)
ES6 引入rest 参数,用于获取函数的实参,用来代替arguments
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
function sum (first, ...args) {
console.log(first); // 10
console.log(args); // [20, 30]
}
sum(10, 20, 30)
如果函数的最后一个命名参数以...
为前缀,则它将成为一个由剩余参数组成的真数组,其中从0
(包括)到``args.length`(排除)的元素由传递给函数的实际参数提供。
在上面的例子中,args
将收集该函数的第2个参数(因为第1个参数被映射到first
)和所有后续参数。
剩余参数和arguments对象的区别
剩余参数和 arguments
对象之间的区别主要有三个:
- 剩余参数只包含那些没有对应形参的实参,而
arguments
对象包含了传给函数的所有实参。 arguments
对象不是一个真正的数组,而剩余参数是真正的Array
实例,也就是说你能够在它上面直接使用所有的数组方法,比如sort
,map
,forEach
或pop
。arguments
对象还有一些附加的属性 (如callee
属性)。
解构剩余参数
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
function f(...[a, b, c]) {
return a + b + c;
}
f(1) // NaN (b and c are undefined)
f(1, 2, 3) // 6
f(1, 2, 3, 4) // 6 (the fourth parameter is not destructured)
剩余参数是真正的 Array
实例,可以直接使用所有的数组方法
1)因为theArgs
是个数组,所以你可以使用length
属性得到剩余参数的个数:
function fun1(...theArgs) {
alert(theArgs.length);
}
fun1(); // 弹出 "0", 因为theArgs没有元素
fun1(5); // 弹出 "1", 因为theArgs只有一个元素
fun1(5, 6, 7); // 弹出 "3", 因为theArgs有三个元素
2)下例中,剩余参数包含了从第二个到最后的所有实参,然后用第一个实参依次乘以它们:
function multiply(multiplier, ...theArgs) {
return theArgs.map(function (element) {
return multiplier * element;
});
}
var arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
3)下例演示了你可以在剩余参数上使用任意的数组方法,而arguments
对象不可以:
function sortRestArgs(...theArgs) {
var sortedArgs = theArgs.sort();
return sortedArgs;
}
alert(sortRestArgs(5,3,7,1)); // 弹出 1,3,5,7
function sortArguments() {
var sortedArgs = arguments.sort();
return sortedArgs; // 不会执行到这里
}
alert(sortArguments(5,3,7,1)); // 抛出TypeError异常:arguments.sort is not a function
8. spread 扩展运算符
扩展运算符(spread)也是三个点(…)。它好比 rest 参数的逆运算,将一个数组或者对象转为用逗号分隔的参数序列,对数组进行解包
let ary = [1, 2, 3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3
扩展运算符可以应用于合并数组。
//将 spread 运算符的用法与 concat 方法的用法进行了对比。
let a = [1,2,3];
let b = "dog";
let c = [42, "cat"];
// Using the concat method.
d = a.concat(b, c);
// Using the spread operator.
e = [...a, b, ...c];
console.log(d);
console.log(e);
// Output:
// 1, 2, 3, "dog", 42, "cat"
// 1, 2, 3, "dog", 42, "cat"
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二: 通过push函数,将一个数组添加到另一个数组的尾部
ary1.push(...ary2);
用于函数调用
function add(x, y) {
return x + y;
}
var numbers = [4, 38];
add(...numbers) // 42
扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法。
Math.max.apply(null, [14, 3, 77]) // ES5 的写法
Math.max(...[14, 3, 77]) // ES6 的写法
将类数组或可遍历对象转换为真正的数组
var oDivs = document.getElementsByTagName('div');
console.log(oDivs)
var ary = [...oDivs];
ary.push('a');
console.log(ary); //Array(7)