Appearance
4.1 ES6 函数
函数参数的扩展
默认参数
基本用法
javascript
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",18); // Amy,18
fn("Amy",""); // Amy,
fn("Amy"); // Amy,17
注意点:使用函数默认参数时,不允许有同名参数。
javascript
// 不报错
function fn(name,name){
console.log(name);
}
// 报错
//SyntaxError: Duplicate parameter name not allowed in this context
function fn(name,name,age=17){
console.log(name+","+age);
}
只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。
javascript
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",null); // Amy,null
函数参数默认值存在暂时性死区,在函数参数默认值表达式中,还未初始化赋值的参数值无法作为其他参数的默认值。
javascript
function f(x,y=x){
console.log(x,y);
}
f(1); // 1 1
function f(x=y){
console.log(x);
}
f(); // ReferenceError: y is not defined
不定参数
不定参数用来表示不确定参数个数,形如,...变量名,由...加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。
基本用法
javascript
function f(...values){
console.log(values.length);
}
f(1,2); //2
f(1,2,3,4); //4
箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体
基本用法:
javascript
var f = v => v;
//等价于
var f = function(a){
return a;
}
f(1); //1
当箭头函数没有参数或者有多个参数,要用 () 括起来。
javascript
var f = (a,b) => a+b;
f(6,2); //8
当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
javascript
var f = (a,b) => {
let result = a+b;
return result;
}
f(6,2); // 8
当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
javascript
// 报错
var f = (id,name) => {id: id, name: name};
f(6,2); // SyntaxError: Unexpected token :
// 不报错
var f = (id,name) => ({id: id, name: name});
f(6,2); // {id: 6, name: 2}
注意点:没有 this、super、arguments 和 new.target 绑定。
javascript
var func = () => {
// 箭头函数里面没有 this 对象,
// 此时的 this 是外层的 this 对象,即 Window
console.log(this)
}
func(55) // Window
var func = () => {
console.log(arguments)
}
func(55); // ReferenceError: arguments is not defined
箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。
javascript
function fn(){
setTimeout(()=>{
// 定义时,this 绑定的是 fn 中的 this 对象
console.log(this.a);
},0)
}
var a = 20;
// fn 的 this 对象为 {a: 18}
fn.call({a: 18}); // 18
不可以作为构造函数,也就是不能使用 new 命令,否则会报错
适合使用的场景
ES6 之前,JavaScript 的 this 对象一直很令人头大,回调函数,经常看到 var self = this 这样的代码,为了将外部 this 传递到回调函数中,那么有了箭头函数,就不需要这样做了,直接使用 this 就行。
javascript
// 回调函数
var Person = {
'age': 18,
'sayHello': function () {
setTimeout(function () {
console.log(this.age);
});
}
};
var age = 20;
Person.sayHello(); // 20
var Person1 = {
'age': 18,
'sayHello': function () {
setTimeout(()=>{
console.log(this.age);
});
}
};
var age = 20;
Person1.sayHello(); // 18
所以,当我们需要维护一个 this 上下文的时候,就可以使用箭头函数。
不适合使用的场景
定义函数的方法,且该方法中包含 this
javascript
var Person = {
'age': 18,
'sayHello': ()=>{
console.log(this.age);
}
};
var age = 20;
Person.sayHello(); // 20
// 此时 this 指向的是全局对象
var Person1 = {
'age': 18,
'sayHello': function () {
console.log(this.age);
}
};
var age = 20;
Person1.sayHello(); // 18
// 此时的 this 指向 Person1 对象
需要动态 this 的时候
javascript
var button = document.getElementById('userClick');
button.addEventListener('click', () => {
this.classList.toggle('on');
});
button 的监听函数是箭头函数,所以监听函数里面的 this 指向的是定义的时候外层的 this 对象,即 Window,导致无法操作到被点击的按钮对象。
4.3 ES6 Class 类
概述
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。
class 的本质是 function。
它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
基础用法
类定义
类表达式可以为匿名或命名。
javascript
// 匿名类
let Example = class {
constructor(a) {
this.a = a;
}
}
// 命名类
let Example = class Example {
constructor(a) {
this.a = a;
}
}
类声明
javascript
class Example {
constructor(a) {
this.a = a;
}
}
注意要点:不可重复声明。
javascript
class Example{}
class Example{}
// Uncaught SyntaxError: Identifier 'Example' has already been
// declared
let Example1 = class{}
class Example{}
// Uncaught SyntaxError: Identifier 'Example' has already been
// declared
注意要点
类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
类中方法不需要 function 关键字。
方法间不能加分号。
javascript
new Example();
class Example {}
类的主体
属性
prototype
ES6 中,prototype 仍旧存在,虽然可以直接自类中定义方法,但是其实方法还是定义在 prototype 上的。 覆盖方法 / 初始化时添加方法
javascript
Example.prototype={
//methods
}
添加方法
javascript
Object.assign(Example.prototype,{
//methods
})
静态属性
静态属性:class 本身的属性,即直接定义在类内部的属性( Class.propname ),不需要实例化。 ES6 中规定,Class 内部只有静态方法,没有静态属性。
javascript
class Example {
// 新提案
static a = 2;
}
// 目前可行写法
Example.b = 2;
公共属性
javascript
class Example{}
Example.prototype.a = 2;
实例属性
实例属性:定义在实例对象( this )上的属性。
javascript
class Example {
a = 2;
constructor () {
console.log(this.a);
}
}
name 属性
返回跟在 class 后的类名(存在时)。
javascript
let Example=class Exam {
constructor(a) {
this.a = a;
}
}
console.log(Example.name); // Exam
let Example=class {
constructor(a) {
this.a = a;
}
}
console.log(Example.name); // Example
方法
constructor 方法
constructor 方法是类的默认方法,创建类的实例化对象时被调用。
javascript
class Example{
constructor(){
console.log('我是constructor');
}
}
new Example(); // 我是constructor
返回对象
javascript
class Test {
constructor(){
// 默认返回实例对象 this
}
}
console.log(new Test() instanceof Test); // true
class Example {
constructor(){
// 指定返回对象
return new Test();
}
}
console.log(new Example() instanceof Example); // false
静态方法
javascript
class Example{
static sum(a, b) {
console.log(a+b);
}
}
Example.sum(1, 2); // 3
原型方法
javascript
class Example {
sum(a, b) {
console.log(a + b);
}
}
let exam = new Example();
exam.sum(1, 2); // 3
实例方法
javascript
class Example {
constructor() {
this.sum = (a, b) => {
console.log(a + b);
}
}
}
类的实例化
new
class 的实例化必须通过 new 关键字。
javascript
class Example {}
let exam1 = Example();
// Class constructor Example cannot be invoked without 'new'
实例化对象
共享原型对象
javascript
class Example {
constructor(a, b) {
this.a = a;
this.b = b;
console.log('Example');
}
sum() {
return this.a + this.b;
}
}
let exam1 = new Example(2, 1);
let exam2 = new Example(3, 1);
// __proto__ 已废弃,不建议使用
// console.log(exam1.__proto__ == exam2.__proto__);
console.log(Object.getPrototypeOf(exam1) === Object.getPrototypeOf(exam2));// true
Object.getPrototypeOf(exam1).sub = function() {
return this.a - this.b;
}
console.log(exam1.sub()); // 1
console.log(exam2.sub()); // 2
decorator
decorator 是一个函数,用来修改类的行为,在代码编译时产生作用。
类修饰
一个参数
第一个参数 target,指向类本身。
javascript
function testable(target) {
target.isTestable = true;
}
@testable
class Example {}
Example.isTestable; // true
多个参数——嵌套实现
javascript
function testable(isTestable) {
return function(target) {
target.isTestable=isTestable;
}
}
@testable(true)
class Example {}
Example.isTestable; // true
实例属性
上面两个例子添加的是静态属性,若要添加实例属性,在类的 prototype 上操作即可。
方法修饰
3个参数:target(类的原型对象)、name(修饰的属性名)、descriptor(该属性的描述对象)。
javascript
class Example {
@writable
sum(a, b) {
return a + b;
}
}
function writable(target, name, descriptor) {
descriptor.writable = false;
return descriptor; // 必须返回
}
修饰器执行顺序
由外向内进入,由内向外执行。
javascript
class Example {
@logMethod(1)
@logMethod(2)
sum(a, b){
return a + b;
}
}
function logMethod(id) {
console.log('evaluated logMethod'+id);
return (target, name, desctiptor) => console.log('excuted logMethod '+id);
}
// evaluated logMethod 1
// evaluated logMethod 2
// excuted logMethod 2
// excuted logMethod 1
封装与继承
getter / setter
定义
javascript
class Example{
constructor(a, b) {
this.a = a; // 实例化时调用 set 方法
this.b = b;
}
get a(){
console.log('getter');
return this.a;
}
set a(a){
console.log('setter');
this.a = a; // 自身递归调用
}
}
let exam = new Example(1,2); // 不断输出 setter ,最终导致 RangeError
class Example1{
constructor(a, b) {
this.a = a;
this.b = b;
}
get a(){
console.log('getter');
return this._a;
}
set a(a){
console.log('setter');
this._a = a;
}
}
let exam1 = new Example1(1,2); // 只输出 setter , 不会调用 getter 方法
console.log(exam1._a); // 1, 可以直接访问
getter 不可单独出现
javascript
class Example {
constructor(a) {
this.a = a;
}
get a() {
return this.a;
}
}
let exam = new Example(1); // Uncaught TypeError: Cannot set property // a of #<Example> which has only a getter
getter 与 setter 必须同级出现
javascript
class Father {
constructor(){}
get a() {
return this._a;
}
}
class Child extends Father {
constructor(){
super();
}
set a(a) {
this._a = a;
}
}
let test = new Child();
test.a = 2;
console.log(test.a); // undefined
class Father1 {
constructor(){}
// 或者都放在子类中
get a() {
return this._a;
}
set a(a) {
this._a = a;
}
}
class Child1 extends Father1 {
constructor(){
super();
}
}
let test1 = new Child1();
test1.a = 2;
console.log(test1.a); // 2
extends
通过 extends 实现类的继承。
javascript
class Child extends Father { ... }
super
子类 constructor 方法中必须有 super ,且必须出现在 this 之前。
javascript
class Father {
constructor() {}
}
class Child extends Father {
constructor() {}
// or
// constructor(a) {
// this.a = a;
// super();
// }
}
let test = new Child(); // Uncaught ReferenceError: Must call super
// constructor in derived class before accessing 'this' or returning
// from derived constructor
调用父类构造函数,只能出现在子类的构造函数。
javascript
class Father {
test(){
return 0;
}
static test1(){
return 1;
}
}
class Child extends Father {
constructor(){
super();
}
}
class Child1 extends Father {
test2() {
super(); // Uncaught SyntaxError: 'super' keyword unexpected
// here
}
}
调用父类方法, super 作为对象,在普通方法中,指向父类的原型对象,在静态方法中,指向父类
javascript
class Child2 extends Father {
constructor(){
super();
// 调用父类普通方法
console.log(super.test()); // 0
}
static test3(){
// 调用父类静态方法
return super.test1+2;
}
}
Child2.test3(); // 3
注意要点
不可继承常规对象。
javascript
var Father = {
// ...
}
class Child extends Father {
// ...
}
// Uncaught TypeError: Class extends value #<Object> is not a constructor or null
// 解决方案
Object.setPrototypeOf(Child.prototype, Father);
4.4 ES6 模块
概述
在 ES6 前, 实现模块化使用的是 RequireJS 或者 seaJS(分别是基于 AMD 规范的模块化库, 和基于 CMD 规范的模块化库)。
ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 的模块化分为导出(export) @与导入(import)两个模块。
特点
ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;。
模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。
export 与 import
基本用法
模块导入导出各种类型的变量,如字符串,数值,函数,类。
- 导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。
- 不仅能导出声明还能导出引用(例如函数)。
- export 命令可以出现在模块的任何位置,但必需处于模块顶层。
- import 命令会提升到整个模块的头部,首先执行。
javascript
/*-----export [test.js]-----*/
let myName = "Tom";
let myAge = 20;
let myfn = function(){
return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass = class myClass {
static a = "yeah!";
}
export { myName, myAge, myfn, myClass }
/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!
建议使用大括号指定所要输出的一组变量写在文档尾部,明确导出的接口。
函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。
as 的用法
export 命令导出的接口名称,须和模块内部的变量有一一对应关系。
导入的变量名,须和导出的接口名称相同,即顺序可以不一致。
javascript
/*-----export [test.js]-----*/
let myName = "Tom";
export { myName as exportName }
/*-----import [xxx.js]-----*/
import { exportName } from "./test.js";
console.log(exportName);// Tom
使用 as 重新定义导出的接口名称,隐藏模块内部的变量
/*-----export [test1.js]-----*/
let myName = "Tom";
export { myName }
/*-----export [test2.js]-----*/
let myName = "Jerry";
export { myName }
/*-----import [xxx.js]-----*/
import { myName as name1 } from "./test1.js";
import { myName as name2 } from "./test2.js";
console.log(name1);// Tom
console.log(name2);// Jerry
不同模块导出接口名称命名重复, 使用 as 重新定义变量名。
import 命令的特点
只读属性:不允许在加载模块的脚本里面,改写接口的引用指向,即可以改写 import 变量类型为对象的属性值,不能改写 import 变量类型为基本类型的值。
javascript
import {a} from "./xxx.js"
a = {}; // error
import {a} from "./xxx.js"
a.foo = "hello"; // a = { foo : 'hello' }
单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import 。
javascript
import { a } "./xxx.js";
import { a } "./xxx.js";
// 相当于 import { a } "./xxx.js";
import { a } from "./xxx.js";
import { b } from "./xxx.js";
// 相当于 import { a, b } from "./xxx.js";
静态执行特性:import 是静态执行,所以不能使用表达式和变量。
javascript
import { "f" + "oo" } from "methods";
// error
let module = "methods";
import { foo } from module;
// error
if (true) {
import { foo } from "method1";
} else {
import { foo } from "method2";
}
// error
export default 命令
- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
- export default 中的 default 是对应的导出接口变量。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 向外暴露的成员,可以使用任意变量来接收。
javascript
var a = "My name is Tom!";
export default a; // 仅有一个
export default var c = "error";
// error,default 已经是对应的导出变量,不能跟着变量声明语句
import b from "./xxx.js"; // 不需要加{}, 使用任意变量接收
复合使用
注:import() 是提案,这边暂时不延伸讲解。
export 与 import 可以在同一模块使用,使用特点:
- 可以将导出接口改名,包括 default。
- 复合使用 export 与 import ,也可以导出全部,当前模块导出的接口会覆盖继承导出的。
javascript
export { foo, bar } from "methods";
// 约等于下面两段语句,不过上面导入导出方式该模块没有导入 foo 与 bar
import { foo, bar } from "methods";
export { foo, bar };
/* ------- 特点 1 --------*/
// 普通改名
export { foo as bar } from "methods";
// 将 foo 转导成 default
export { foo as default } from "methods";
// 将 default 转导成 foo
export { default as foo } from "methods";
/* ------- 特点 2 --------*/
export * from "methods";