ES6 语法
1 块作用域构造 let 和 const
1.1 块作用域
块级声明用于生命在指定快的作用域之外无法访问的变量。块级作用域存在于:
- 函数内部
- 块中(大括号之间的区域)
1.2 let声明
在 ES6 之前,我们声明一个变量通常使用 var 关键字,但 var 关键字有一个特点:
无论在哪里声明,都会被当成在当前作用域顶部声明的变量,这也是JavaScript的变量提升机制。比如下面两段代码是等价的。
var a;
console.log("a还没有赋值的值:", a); // a还没有赋值的值: undefined
a = 1;
console.log("a赋值过的值", a); // a赋值过的值 1
---------------------------------------------------------------
console.log("a还没有赋值的值:", a); // a还没有赋值的值: undefined 这里a声明在后面却依然有值,就是因为var的这个特点
var a = 1;
console.log("a赋值过的值", a); // a赋值过的值 1
在一些情况下,我们希望定义的变量不被提升,由此 let 关键字就出现了, let 关键字可以将作用域限制在当前代码块当中。比如我们用 let 替换上述代码块中的var,运行结果的区别。
let a;
console.log("a还没有赋值的值:", a); // a还没有赋值的值: undefined
a = 1;
console.log("a赋值过的值", a); // a赋值过的值 1
---------------------------------------------------------------------
console.log("a还没有赋值的值:", a); // 这里会因为a没有定义而报错
let a = 1;
console.log("a赋值过的值", a); // 自然就不会走到下面来了
let 与 var 的另一个区别是 var 可以重复定义,而 let 不行,比如:
var a = 0;
console.log(a); // 0
var a = 10;
console.log(a); // 10 是OK的
---------------------------------------------------------------------------
let b = 0;
console.log(b);
let b = 10; // 语法错误 Uncaught SyntaxError: Identifier 'b' has already been declared.
console.log(b);
接上文,还有一种特殊情况,当 let 重复定义的变量处于两个不同的作用域时,则不会报错,这点需要特别关注。
let b = 0;
console.log(b); // 0
{
let b = 10;
console.log(b); // 10
}
1.3 const 声明
const 关键字时ES6提供的另一个用于定义变量的关键字,通过 const 声明的常量必须在声明的同时进行初始化,比如:
const a = 10;
console.log(a); // 10
const b; // 语法报错 'const' declarations must be initialized.
b = 100;
console.log(b);
与 let 相同,在同一作用域下, const 不能重复定义,无论先前是用 var、let 还是 const 声明,如:
var a = 10;
const a = 20; // Uncaught SyntaxError: Identifier 'a' has already been declared
-------------------------------------------------------------------------------
let b = 10;
const b = 20; // Uncaught SyntaxError: Identifier 'b' has already been declared
-------------------------------------------------------------------------------
const c = 10;
const c = 20; // Uncaught SyntaxError: Identifier 'c' has already been declared
又如 let ,在不同作用域下,是可以重复定义的
var a = 10;
console.log(a); // 10
{
const a = 20;
console.log(a); // 20
}
使用 const 声明对象时,对象本身的绑定是不能修改的,但对象的属性和值是可以修改的,比如:
const kid = {
name:"XiaoMing",
age:10
}
console.log(kid); // {name: 'XiaoMing', age: 10}
kid.name = "XiaoGang";
kid.age = 12;
console.log(kid); // {name: 'XiaoGang', age: 12}
-----------------------------------------------------------------
const kid = {
name:"XiaoMing",
age:10
}
console.log(kid); // {name: 'XiaoMing', age: 10}
kid = {
name:"XiaoGang",
age:12
}
console.log(kid); // Uncaught TypeError: Assignment to constant variable.
1.4 全局块作用域绑定
在 JavaScript 中,当我们在全局作用域中使用 var 来声明对象或变量时,它将作为浏览器环境中 window 对象的一个属性,这就意味着如果我们声明了原本 window 属性中就有的名字,就会将原来的属性值覆盖,比如:
// Screen 是 window 中原本就存在的属性
console.log(window.Screen); // ƒ Screen() { [native code] }
//这时我们再用 var 声明一个 Screen ,就会将原来的覆盖
var Screen = "Sreen of mine";
console.log(window.Screen); // Screen of mine
如果使用 let 或者 const 则不会有以上问题产生,比如:
// Screen 和 Audio 是 window 中原本就存在的属性
console.log(window.Screen); // ƒ Screen() { [native code] }
console.log(window.Audio); // ƒ Audio() { [native code] }
//这时我们再用 let 声明一个 Screen ,用 const 声明一个Audio,也不会将其覆盖
let Screen = "Sreen of mine";
const Audio = "Audio of mine";
console.log(window.Screen); // ƒ Screen() { [native code] }
console.log(Screen); // Screen of mine
console.log(window.Audio); // ƒ Audio() { [native code] }
console.log(Audio); // Audio of mine
综上所述,如果不想为全局对象window创建属性,或为了避免覆盖,应该尽量使用 let 和 const 来声明变量。
2 模板字面量
2.1 什么是模板字面量?
ES6 引入了模板字面量(Template Literals),它对字符串的操作进行了增强方式:
- 多行字符串:真正的多行字符串
- 字符串占位符:可以将变量或JavaScript表达式嵌入占位符中并将其作为字符串的一部分输出到结果中。
模板字面量的基本语法是用一对反引号(`)来替代单双引号,比如:
let message = `Hello World!`
当需要在字串中使用反引号时,可以使用反斜杠(\)将其转义,而单双引号在模板字面量中则不需要转义,比如:
let message = `Hello\` World`;
console.log(message); // Hello` World
let message2 = `Hello" World'`;
console.log(message2); // Hello" World'
2.2 多行字符串
在 ES6 之前,如果一个字符串字面量需要多行书写,可以采取两种方法来实现:
- 在一行结尾的时候添加反斜杠(、)来表示承接下一行的代码
- 使用加号(+)拼接字符串
let message = "Hello \
World";
console.log(message); // Hello World
----------------------------------------------------------------
let message2 = "Hello "
+ "World";
console.log(message2); // Hello World
而这两种方法只是代表了行的延续,而在打印时并没有真正的换行,需要在其后增加换行符(\n),比如:
let message = "Hello \n\
World";
console.log(message); /*Hello
World*/
----------------------------------------------------------------
let message2 = "Hello "
+"\n"
+ "World";
console.log(message2); /*Hello
World*/
在 ES6 中,使用模板字面量语法,就可以很方便地实现多行字符串,只需要在需要换行的地方真正换行即可,比如:
let message =
`Hello
World`;
console.log(message); /*Hello
World*/
但需要注意的是,在反引号之中的所有空白字符,包括空格、换行、制表符等,都属于字符串的一部分,因此考虑到代码的缩进,使用时偶尔需要对字符串进行特殊处理。
2.3 字符串占位符
在一个模板字面量中,可以将 JavaScript 变量或者任何合法的 JavaScript 表达式嵌入占位符中并将其作为字符串的一部分输出到结果中。
占位符的基本语法为${},在大括号之间存放需要占位的变量或表达式,比如:
let name = "XiaoWang";
let message = `Hello, ${name}`;
console.log(message); // Hello, XiaoWang
let amount = 5;
let price = 6;
let message2 = `The total price is ${amount * price}`;
console.log(message2); // The total price is 30
3 默认参数
在 ES5 中,没有提供在参数列表中指定默认参数的语法,要想为函数添加默认值,只能通过如下代码实现:
function makeRedirect(url, timeout) {
url = url || "/home";
timeout = timeout || 2000;
// 函数的其他部分
}
这种写法里,url 和 timeout 是可选参数,如果不传入对应的参数值,它们也将被赋予一个默认值。但要考虑一种特殊情况,如果形参 timeout 传入值 0,在逻辑判断中也会被视为一个假值(false),从而将参数传入为2000。
为避免上述情况的发生,我们改进了写法,通过 typeof 检查参数类型后决定传入的参数,比如:
function makeRedirect(url, timeout) {
url = (typeof url != "undefined") ? url : "/home";
timeout = (typeof timeout != "undefined") ? timeout : 2000;
// 函数的其他部分
}
但是在 ES6 中,提供了更为简便的写法,代码如下:
function makeRedirect(url ="/home", timeout = 2000) {
// 函数的其他部分
}
与其他可以传入默认值参数的语言不同,在 ES6 中,声明函数时,可以为任意参数指定默认值,比如:
// 可以为前两个参数指定默认值,而第三个参数不指定
function makeRedirect(url ="/home", timeout = 2000, callback){
//函数其他部分
}
这种情况下,只有在两种情况下会使用默认值:
- 没有为 url 和 timeout 传值
- 为 url 和 timeout 传入 undefined
4 rest 参数
JavaScript 函数有一个特殊的特性:无论在函数定义中声明了几个形参,在传参时可以传入任意数量的参数,而所有参数的集合可以用arguments表示,比如:
function calculate(op) {
if (op === '+') {
let result = 0;
for (let i = 1; i<arguments.length;i++){
result += arguments[i];
}
return result;
}
}
let result = calculate('+', 1,2,3); // 传入了四个参数,但是声明函数时只需要一个,因此op取第一个'+',其余参数可从arguments[1]开始依次后取
console.log(result); // 6
ES6 引入了 rest 参数,在函数的命名参数前添加三个点(...),这就表明是一个 rest 参数,用于获取函数的多余函数,由此可以重写上面的代码为:
function calculate(op, ...data) {
if (op === '+') {
let result = 0;
for (let i = 0; i<data.length;i++){ //此处 i 可以从 0 开始计算了
result += data[i];
}
return result;
}
}
let result = calculate('+', 1,2,3); // rest 参数为1,2,3
console.log(result); // 6
需要注意的是, 每个函数最多只能有一个 rest 参数,而且 rest 参数必须是最后一个参数,比如下面是错误的写法:
function calculate(op, ...data, last) { // 语法错误 A rest parameter must be last in a parameter list.
}
5 展开运算符
展开运算符与 rest 参数写法相同,都是三个点(...)。但展开运算符是将数组转为独立参数,或将对象的所有可遍历属性取出,而 rest 参数是整合为一个整体数组。
展开运算符的具体用法可通过下面的代码来看:
function sum(a,b,c){
return a + b +c;
}
let arr = [1,2,3];
// 如果想将 arr 数组中的三个数字分别作为三个形参传入
// 如果直接将 arr 作为参数
let arrSum1 = sum (arr);
console.log(arrSum1); // 1,2,3undefinedundefined [1,2,3]作为一个整体,而后面两个参数没有传值为undefined
// 往常的做法是,将数组中的数据取出
let arrSum2 = sum(arr[0], arr[1], arr[2]);
console.log(arrSum2); // 6 结果正确,但过程过于麻烦
// 如果使用展开运算符,代码就会很清爽
let arrSum3 = sum(...arrr);
console.log(arrSum3); // 6
展开运算符可以用作数组的拷贝,比如:
let arr1 = [1, 2, 3];
let arr2 = arr1;
console.log(arr2); // (3) [1, 2, 3]
let arr3 = [...arr1];
console.log(arr3); // (3) [1, 2, 3]
// 那么 arr2 和 arr3 有什么区别呢?arr2 的方法好像还更简洁?
// 此时我们改变 arr1 的值,再次输出 arr2 和 arr3
arr1[0] = 10;
console.log(arr2); // (3) [1, 2, 3]
console.log(arr3); // (3) [1, 2, 3]
// 可以看到,arr2 和 arr1 就是同一个数组对象
展开运算符也可以用作数组的合并,比如:
let arr1 = [1, 2, 3];
let arr2 = ['a','b','c'];
let arr3 = ['d','e'];
console.log(...arr1, ...arr2, ...arr3); // 1 2 3 'a' 'b' 'c' 'd' 'e'
展开运算符还可以用于去除对象的所有可遍历对象,并可以复制到另一个对象中,比如:
let student = {
name:"XiaoWang",
age:21
}
let person = {
...student,
desc:"一个人"
}
console.log(person); // {name: 'XiaoWang', age: 21, desc: '一个人'}
6 对象字面量语法扩展
6.1 属性初始值的简写
一句话说,就是如果属性的 key 和 value 同名,那么可以只写一个,比如:
// 简写前
function createStu(name, age){
return {
name: name,
age: age
}
}
// 简写后
function createStu(name, age){
return {
name,
age
}
}
另外,当有同名的本地变量时,也可以只写属性名,比如:
let name = "XiaoWang";
let age = 22;
var student = {name, age};
当对象字面量里只有属性名称时,JavaScript 引擎会在可访问作用域中查找其同名的变量,如果找到了,就会把值赋给对象字面量里的同名属性。
6.2 对象方法的简写
同样用一句话说,对象方法可以省略冒号和 function 关键字,注意是仅限对象方法,全局方法仍然需要function关键字,比如:
// 简写前
var car = {
shwoColor:function(){
console.log("red");
}
}
// 简写后
var car = {
shwoColor(){
console.log("red")
}
}
通过简写的方法有一个name属性,可返回方法名
6.3 可计算的属性名
在 ES6 之前的 JavaScript 语法中,访问对象的属性可以通过点号或方括号,但如果属性名包括了特殊字符或中文,亦或者需要计算得到属性名,则只能使用方括号,比如:
let a = 'name';
let person = {};
person["first name"] = "san";
person["last " + a] = "zhang";
console.log(person); // {first name: 'san', last name: 'zhang'}
//下面展示错误的写法(ES5之前)
let a = 'name';
let person = {
["first" + name]:"san",
["last" + name] :"zhang"
}
而在 ES6 之后,上述的错误写法也可以了。
7 解构赋值
ES6 中为对象和数组提供了解构功能,允许按照一定模式从对象和数组中提取值,并对变量进行赋值。
7.1 对象解构
可以从如下代码看到用法:
let book = {
title:"时间简史",
price:55,
author:"霍金"
};
let {title, price, author} = book;
console.log(title); // 时间简史
console.log(price); // 55
console.log(author); // 霍金
但需要注意,用大括号的方式来声明多个变量时,需要在声明时就初始化程序,不然会报语法错误,比如:
let {title, price, author, others} // A destructuring declaration must have an initializer
另外一种情况是,如果想要解构的变量在之前就已经声明,那么在赋值时需要用圆括号将解构赋值语句包裹,比如:
let book = {
title:"时间简史",
price:55,
author:"霍金"
};
let title;
let price;
let author;
{title, price, author} = book; // Declaration or statement expected.
({title, price, author} = book); // 正确
这是因为在 JavaScript 中,JS 引擎将一对花括号内的内容视为一个代码块,而语法规定,代码块语句是不能够出现在赋值语句的左侧的,添加圆括号后,可以将块语句转化为表达式,从而实现赋值。
值得一提的是,在上述情况中,如果想要完成解构赋值,大括号中的变量名与对象中的属性名应该是一一对应且相同的,如果想要以别名赋值,那么可以采用“属性名 : 别名”的写法,比如:
let book = {
title:"时间简史",
price:55,
author:"霍金"
};
let {title:shuming, price:jiage, author:zuozhe} = book;
console.log(shuming); // 时间简史
console.log(jiage); // 55
console.log(zuozhe); // 霍金
当需要嵌套解构时,用到的语法一般是“父属性 : {子属性}”,比如:
let book = {
title:"时间简史",
price:55,
author:"霍金",
category:{
id:1,
name:"宇宙",
child:{
id:12
}
}
};
let {category:{child:{id}}} = book;
console.log(id); // 12
7.2 数组解构
数组解构与对象解构的语法不同,将“{}”替换为“[]”,语法上也更为简单,比如:
let arr = [1,2,3];
let [a,b,c] = arr;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
在数组解构中,不是通过属性名而是通过元素顺序进行选取的,因此可以通过占位符等操作实现只获取某几个元素的操作,比如:
let arr = [1,2,3];
let [ , ,c] = arr;
console.log(c); // 3
另外,与对象解构不同,数组解构已经对生命的本地变量是,不需要是用圆括号包裹,比如:
let arr = [1,2,3];
let a, b, c;
[a,b,c] = arr; // 这种写法是被允许的
嵌套数组解构与嵌套对象解构的语法相类似,代码如下:
let arr = [1,['wang', 'zhang'],3];
let [a,[b,c],d] = arr;
console.log(b); // wang
console.log(c); // zhang
8 箭头函数
ES6 中允许使用箭头(=>)定义函数,根据 JavaScript 函数定义的各种不同形式,函数箭头的参数和函数体分别可以采取不同的形式。
8.1 箭头函数的语法
为方便理解,一下将写出箭头函数与它所等价的函数。
第一种,单一参数、函数体只有一条语句的函数,比如:
function greet(msg){
return msg;
}
console.log(greet("hello")); // hello
--------------------------------------------------------------
let greet = msg => msg; // 前面的greet代表函数名,第一个 msg 代表参数,第二个 msg 代表语句
console.log(greet("hello")); // hello
从上述可以看出,调用箭头函数时,还是需要像普通函数一样采取“函数名(参数)”的形式,如果只打印greet,会将箭头函数的函数体返回,比如:
let greet = (msg) => msg;
console.log(greet); // (msg) => msg
第二种,有多个参数,函数体只有一条语句的函数,需要将参数用圆括号包裹,比如:
function greet(user, msg){
return user + ',' + msg;
}
console.log(greet('Jerry', 'hello')); // Jerry,hello
---------------------------------------------------------------
let greet = (user, msg) => user + ',' + msg
console.log(greet('Jerry', 'hello')); // Jerry,hello
第三种,函数的参数为空,只需要写一对空的圆括号,比如:
function greet(){
return 'hello';
}
console.log(greet()); // hello
------------------------------
let greet = () => 'hello';
console.log(greet());
第四种,函数有多条语句,则需要像普通函数一样,用花括号包裹函数体,比如:
function sum(a,b){
let c = a + b;
return c;
}
console.log(sum(1,2)); // 3
--------------------------------------
let sum = (a,b) =>{
let c = a + b;
return c;
}
console.log(sum(1,2)); // 3
第五种,如果要创建一个空函数,只需要写一对空的圆括号代表参数部分和一对空的花括号代表函数体部分,比如:
function empty(){}
------------------
let empty = () => {};
第六种,如果函数的返回值时一个对象字面量,则需要将字面量包裹在一对圆括号中,比如:
function car(color, price){
return {
color:color,
price:price
}
}
console.log(car('yellow',24)); // {color: 'yellow', price: 24}
--------------------------------------------------------------
let car = (color, price) => ({color:color,price:price});
console.log(car('yellow',24)); // {color: 'yellow', price: 24}
8.2 箭头函数与this
JavaScript 中的 this 与其他高级语言的 this 有所不同, JavaScript 中的 this 并不是指向对象本身,其指向是可以改变的,是根据当前执行上下文的变化而变化。可以根据下面一段代码来感受上下文是如何影响 this 的指向:
var animal = 'dog';
function getAnimal(){
console.log(this.animal);
}
var obj = {
animal:'cat',
getAnimal:getAnimal
}
getAnimal(); // dog 这里相当于执行window.getAnimal(),因此this指向的是window对象,animal为window对象的属性
obj.getAnimal(); // cat 函数内部的this指向obj对象,animal为obj对象的属性
var getAnimal2 = obj.getAnimal;
getAnimal2(); // dog 虽然该函数由obj.getAnimal赋值得到,但执行时,上下文对象是window,所以属性也是window的
为了解决 this 指向的问题,我们可以使用 bind() 方法,将 this 明确的绑定到某个对象上,例如修改上述代码:
var animal = 'dog';
function getAnimal(){
console.log(this.animal);
}
var obj = {
animal:'cat',
getAnimal:getAnimal
}
getAnimal(); // dog 这里相当于执行window.getAnimal(),因此this指向的是window对象,animal为window对象的属性
obj.getAnimal(); // cat 函数内部的this指向obj对象,animal为obj对象的属性
var getAnimal2 = obj.getAnimal.bind(obj);
getAnimal2(); // cat 将该方法绑定到obj,this指向也是obj
使用 bind() 方法实际上是创建了一个新的函数,称为绑定函数,该函数的 this 被绑定到参数传入的对象。为了不创建一个额外的函数,我们通常选择使用箭头函数来解决 this 的问题。
箭头函数中没有 this 绑定,必须通过查找作用域来决定它的值。如果箭头函数被一个非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this ;否则, this 的值会被设置为全局对象,比如下面两段代码的比较:
var animal = 'dog';
var obj = {
animal:'cat',
getAnimal(){
setTimeout((function(){console.log(this.animal)}), 2000);
}
}
obj.getAnimal(); // dog setTimeout参数中的匿名函数的上下文是window对象,animal也是window的animal
------------------------------------------------------------------------------------------------
var animal = 'dog';
var obj = {
animal:'cat',
getAnimal(){
setTimeout(()=>console.log(this.animal),2000);
}
}
obj.getAnimal(); //cat 箭头函数的this与getAnimal()方法中的this一直,指向obj对象
另外需要说明的是,箭头函数的 this 指向只取决于该函数外部非箭头函数的 this 值,不能通过 call()、 apply()、 bind() 方法来改变。
8.3 使用箭头函数的注意事项
- 没有 this、 super、 arguments 和 new.target 绑定。箭头函数的 this 、 super 、 arguments 和 new.target 这些值由外围一层非箭头函数决定。
- 不能通过 new 关键字调用。箭头函数不能被用作构造函数,也就是说,不可以使用 new 关键字调用箭头函数,这样会使程序抛出错误。
- 没有原型。由于不可以使用 new 关键字调用, 箭头函数也就没有构造圆形的需求,所以箭头函数不存在 prototype 这个属性。
- 不可以改变 this 的绑定。函数内部的 this 值不可被改编,在函数的生命周期内始终保持一致。
- 不支持 arguments 对象。箭头函数没有 arguments 绑定,所以只能通过命名参数和 rest 参数这两种方式访问函数的参数。
9 类
ES6 引入了类(class)的概念,使得 JavaScript 支持了类和类继承的特性,也让对象原型的写法更加清晰,也更像传统的面向对象编程语言的写法。
9.1 类的定义
ES6 之前,并没有类的概念,我们定义类通常采用构造函数和原型混合的方式,比如:
function person(name, age){
this.name = name;
this.age = age;
}
person.prototype.showName = function(){
console.log(this.name);
}
var student = new person("xiaowang", 22);
student.showName();
ES6 之后,引入了 class 关键字,使得类的定义更加接近 Java 、 C++ 等面向对象语言中的写法。用 ES6 语法改写上述代码:
class person{
// 构造函数
constructor(name, age){
this.name = name;
this.age = age;
}
// 等价于person.prototype.showName
showName(){
console.log(this.name);
}
}
let student = new person("xiaowang", 22);
student.showName();
这里要引入一个自有属性的概念。自有属性是对象实例的属性,不会出现在原型上,比如上面的 name 和 age。自有属性只能在类的构造函数或方法中创建,一般建议在构造函数中创建所有的自有属性,当你试图在构造器之外声明变量或属性时,会抛出语法错误,可以参考如下代码:
class person{
// 构造函数
constructor(name, age){
this.name = name;
this.age = age;
}
let desc = '不错'; // Unexpected token. A constructor, method, accessor, or property was expected.
// 等价于person.prototype.showName
showName(){
console.log(this.name);
}
}
------------------------------------------------------------------------------------------------------
class person{
// 构造函数
constructor(name, age){
this.name = name;
this.age = age;
}
// 等价于person.prototype.showName
showName(){
console.log(this.name);
}
}
console.log(person.prototype.name); // undefined. 构造器中的自有属性,prototype中没有
console.log(person.prototype.showName); /*ƒ showName(){
console.log(this.name);
} 构造器外的方法,在prototype上
*/
与函数一样,类也可以用表达式的形式定义,比如:
let person = class{
// 构造函数
constructor(name, age){
this.name = name;
this.age = age;
}
// 等价于person.prototype.showName
showName(){
console.log(this.name);
}
}
let student = new person("xiaowang", 22);
student.showName();
以上方法还有一种特殊写法,可以在定义后立即创建一个实例对象,比如:
let person = class{
// 构造函数
constructor(name, age){
this.name = name;
this.age = age;
}
// 等价于person.prototype.showName
showName(){
console.log(this.name);
}
}("xiaowang",22);
person.showName();
9.2 访问器属性
访问器属性是通过 get 和 set 关键字创建的,语法为关键字 get 或 set 后面跟一个空格和相应的标识符,实际上就是某个属性的取值和设值函数,在使用时以属性访问的方法来使用。与自有属性不同的是,访问器属性是在原型上创建的。比如:
class Person{
constructor(name, age){
this._name = name;
this._age = age;
}
// 只读属性
get desc(){
return `${this._name} is a student`;
}
get name(){
return this._name;
}
set name(value){
this._name = value;
}
}
let person = new Person("xiaowang", 4);
console.log(person.name); // xiaowang
console.log(person.desc); // xiaowang is a student
person.name = 'xiaoli';
console.log(person.name); // xiaoli
在构造函数中定义了一个 _name 属性, _name 属性前面的下划线是一种约定记号,表示只能通过对象方法访问的属性。当访问属性 name 时,实际上是调用它的 get 方法,当给属性 name 赋值时,实际上是调用它的 set 方法。
9.3 静态方法
ES6 中引入了关键字 static,用于定义静态方法。出构造函数意外,类中所有的方法和访问器属性都可以用 static 来定义。使用 static 关键字定义的静态方法,只能通过类名访问,不能通过实例访问。比如:
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
static showName() {
console.log('静态方法已运行');
}
}
Person.showName(); // 静态方法已运行
person = new Person('xiaowang', 22);
person.showName(); // Uncaught TypeError: person.showName is not a function
9.4 类的继承
ES5 及早先版本不支持类的继承,而 ES6 提供了 extends 关键字,这样可以很轻松的实现类的继承。比如:
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
work(){
console.log('working')
}
}
class Student extends Person{
constructor (name, age, gender){
super(name, age);
this._gender = gender;
}
}
let stu = new Student('xiaowang', 22, 'boy');
console.log(stu); // Student {_name: 'xiaowang', _age: 22, _gender: 'boy'}
stu.work(); // working
Student 类通过关键字 extends 继承自 Person 类, Student 类称为派生类。在 Student 的构造函数中,通过 super() 来调用 Person 的构造函数并传入相应的参数。要注意的是,如果在派生类中定义了构造函数,则必须调用 super(), 而且一定要在访问 this 之前调用。
如果在派生类中没有定义构造函数,那么当创建派生类的实例时会自动调用 super() 并传入所有参数。例如,下面的代码定义了 Teacher 类,从 Person 类继承,在类声明中,没有定义构造函数。
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
work(){
console.log('working')
}
}
class Teacher extends Person{
}
let teacher = new Teacher('xiaowang', 22);
console.log(teacher); // Teacher {_name: 'xiaowang', _age: 22}
teacher.work(); // working
在派生类中,可以重写基类的方法,这将覆盖基类中的同名方法。比如:
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
work(){
console.log('working')
}
}
class Student extends Person{
constructor (name, age, gender){
super(name, age);
this._gender = gender;
}
work(){
console.log('Student is working')
}
}
let stu = new Student('xiaowang', 22, 'boy');
console.log(stu); // Student {_name: 'xiaowang', _age: 22, _gender: 'boy'}
stu.work(); // Student is working
如果在 Student 的 work() 方法中需要调用基类的 work() 方法,可以使用 super 关键字来调用。比如:
class Student extends Person{
constructor (name, age, gender){
super(name, age);
this._gender = gender;
}
work(){
super.work();
console.log('Student is working')
}
}
10 模块
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单。
一个模块通常是一个独立的 JS 文件,该文件内部定义的变量和函数除非被导出,否则不能被外部所访问 。
10.1 模块导出
使用 export 关键字放置在需要暴露给其他模块使用的变量、函数或者类声明之前,以将他们从模块中导出。
导出时可以分别对变量、函数和类进行导出,比如:
// 单个导出
export var color = 'red';
export let name = 'xiaowang';
export const age = 10;
export function sum(a, b) {
return a + b;
}
也可以在结尾进行导出,比如:
// 单个导出
var color = 'red';
let name = 'xiaowang';
const age = 10;
function sum(a, b) {
return a + b;
}
export {color, name, age};
export {sum as qiuhe};
放在结尾导出时,需要将需要导出的内容包裹在一对花括号中,此外,可以用 as 关键字来规定导出后使用的名称。
还有一种特殊的导出,成为默认值导出,默认值使用 default 关键字进行声明,默认值导出有以下几种写法:
第一种,在结尾写在 export 和 被导出内容之间:
var color = 'red';
let name = 'xiaowang';
const age = 10;
function sum(a, b) {
return a + b;
}
export default color;
可以看到,与非默认值不同,默认值在导出时并不需要花括号包裹。
第二种,写在被导出模块声明之间,比如:
export default function sum(a, b) {
return a + b;
}
第三种,用 as 连接,比如:
var color = 'red';
let name = 'xiaowang';
const age = 10;
export {color as default}
10.2 导入
导入通过 import 关键字实现,写法如下:
// 导入单个绑定
import {sum} from './module.js';
// 导入多个绑定
import {color, name, age} from './module.js'
// 导入默认模块值(假设默认值为color) 同样的,默认模块值的导入不需要花括号
import color from './module.js'
// 导入在导出时重命名的模块(sum as qiuhe)
import {qiuhe} from './module.js'
// 导入模块并重命名
import {sum as qiuhe} from './module.js'
// 导入整个模块
import * as example from './module.js'
11 Promise
11.1 为什么需要 Promise ❓
JavaScript 引擎是基于单线程事件循环的概念构建的,它采用任务队列的方式,将要执行的代码块放到队列中。当 JavaScript 引擎中的一段代码执行结束,事件循环会指定队列中的下一个任务来执行。
JavaScript 执行异步调用的传统方式时时间和回调函数,但伴随着应用的复杂,时间和回调函数无法完全满足开发者想做的事情,也就是我们通常说的回调地狱问题,因此就需要 Promise 登场了。
11.2 Promise 是什么 ❓
一个 promise 可以通过 promise 构造函数来创建,这个构造函数只需要一个参数:包括初始化 promise 代码的执行器(executor)函数,在该函数内包含需要异步执行的代码。执行器函数接收两个参数,分别是 resolve 函数和 reject 函数,这两个函数由 JavaScript 引擎提供,不需要我们自己编写。异步操作结束成功时调用 resolve 函数,失败时调用 reject 函数,例如:
const promise = new Promise(function(resolve, reject) {
// 开启异步操作
setTimeout(function(){
try{
let c = 6 / 0 ;
// 执行成功调用resolve函数
resolve(c);
}catch(ex){
// 执行失败调用reject函数
reject(ex);
}
}, 1000);
});
执行器函数中包含了异步调用,在 1s 后执行两个数的除法运算,如果成功,则用想出结果作为参数调用 resolve 函数,失败则调用 reject 函数。
11.3 Promise 的生命周期
每个 promise 都会经历一个短暂的生命周期:显示处于进行中(pending)的状态,此时操作尚未完成,所以它也是未处理的(unsettled),一旦异步操作执行结束,promise 则变为已处理(settled)状态。操作结束后,根据异步操作执行成功与否,可以进入以下两个状态之一:
- fulfilled: promise 异步操作成功完成。
- rejected: 由于程序错误或者其它一些原因, promise 异步操作未能成功完成,即已失败。
一旦 promise 状态改变,就不会再次发生改变,任何时候都可以得到这个结果。
在 promise 状态改变后,我们怎么根据不同的状态来做不同的处理呢? promise 对象有一个 then() 方法,它接受两个参数:
- 当 promise 的状态变为 fulfilled 时要调用的函数,与异步操作相关的附加数据通过调用 resolve 函数传递给这个完成函数
- 当 promise 的状态变为 rejected 时要调用的函数,所有与失败相关的附加数据通过调用 reject 函数传递给这个拒绝函数
例如:
const promise = new Promise(function(resolve, reject) {
// 开启异步操作
setTimeout(function(){
try{
let c = 6 / 0 ;
// 执行成功调用resolve函数
resolve(c);
}catch(ex){
// 执行失败调用reject函数
reject(ex);
}
}, 1000);
});
promise.then(function(value){
// 成功
console.log(value);
}, function(err){
// 失败
console.error(err.message);
})
当我们只想要成功后处理或失败后处理时,可以给另外一个参数传递 null。比如:
promise.then(null,function(err){
// 拒绝
console.error(err.message);
})
promise 对象还有一个catch() 方法,用于在执行失败后进行处理,等价于上述只给 then() 方法传入拒绝处理函数的代码。代码如下所示:
prommise.catch(function (err){
console.error(err.message);
})
标签:ES6,console,log,age,语法,let,函数,name
From: https://www.cnblogs.com/Edward6/p/17235850.html