十八、Class
示例:ES5
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return ‘(‘ + this.x + ‘, ‘ + this.y + ‘)‘;
};
var p = new Point(1, 2);
示例:ES6的等价写法
class Point{
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return ‘(‘ + this.x + ‘, ‘ + this.y + ‘)‘;
}
}
var p = new Point(1, 2);
实际上,类的所有方法都定义在类的prototype属性上面;在类的实例上面调用方法,其实就是调用原型上的方法。
p.constructor === Point.prototype.constructor; // true
注意:类中定义的方法,都是带有作用域的普通函数,而不是箭头函数。
当然属性名支持表达式方式
let methodName = "toString";
class Point{
constructor(x, y){
this.x = x;
this.y = y;
}
[methodName](){
return ‘(‘ + this.x + ‘, ‘ + this.y + ‘)‘;
}
}
var p = new Point(1, 2);
constructor方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
class Ponit{}
new Ponit();
类的实例对象
必须通过new调用,否则会报错!
var point = Point(2, 3); // 报错
var point = new Point(2, 3); // 正确
不存在变量提升
/* function */
new Foo();
function Foo(){}
/* class */
new Bar(); // Unexpected identifier
Class Bar{};
class表达式
和函数一样,class可以使用表达式的形式定义。通过表达式可以实现立即执行的class。
let person = new class{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}(‘ligang‘);
person.sayName();
继承
class Point2D{
constructor(x, y){
this.x = x;
this.y = y;
}
}
class Point3D extends Point2D{
constructor(x, y, z){
super(x, y);
this.z = z;
}
toString() {
return `$(this.x}, $(this.y}, $(this.z}`;
}
}
console.log(new Point3D(1, 2, 3)); // Point3D { x: 1, y: 2, z: 3 }
注意:一个子类继承了一个父类,那么在子类的constructor中必须使用super调用父类构造函数后才能在子类的constructor中使用this。
class Point2D{}
class Point3D extends Point2D{
constructor(x, y, z){
// super();
this.x = x;
this.y = y;
this.z = z;
}
toString() {
return `$(this.x}, $(this.y}, $(this.z}`;
}
}
console.log(new Point3D(1, 2, 3)); // 报错:this is not defined
取值函数(getter)和存值函数(setter)
在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class Person{
constructor(name){
this.name = name;
}
set username(newName){
this.name = newName;
}
get username(){
return this.name;
}
};
var p = new Person(‘x‘);
console.log(p.username); // ‘x‘
p.username = "ligang";
console.log(p.username); // ‘ligang‘
私有方法
ES6中并没有支持,但是可以通过一些约定方式去实现
方式一:通过命名区分,如函数名增加”_”
方式二:通过Symbol值的唯一性
const method = Symbol(‘sayName‘);
class Person{
constructor(name){
this.name = name;
}
[method](){
console.log(this.name);
}
say(){
this[method]();
}
};
var p = new Person(‘ligang‘);
p.say(); // ‘ligang‘
p.sayName(); // p.sayName is not a function
静态方法
方法前加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Person{
constructor(name){
this.username = name;
}
static sayHello(){
return ‘hello‘;
}
static sayName(){
return this.username;
}
};
console.log(Person.sayHello()); // ‘hello‘
console.log(Person.sayName()); // undefined
注意:静态方法中,this的指向问题!!
问题
(1)不支持私用属性,只能通过一些约定实现
(2)不支持实例属性,只能通过Getter/Setter实现
(3)不支持多重继承
十九、Module
? ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。注意,ES6的模块自动采用严格模式,不管你有没有在模块头部加上”use strict”;。模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
导出模块
? 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
(1)导出单一接口
Sybtax:export <statement>
// module.js
export function method(){}
export class Foo{}
// app.js
import {method, Foo} from ‘module.js‘
export语句需要具有声明部分和赋值部分。
const foo = ‘bar‘;
export foo; // Error
(2)导出模块默认接口
Sybtax:export default <value>
// module.js
export default function() {}
// app.js
import customName from ‘module.js‘
customName();
(3)混合使用导出接口语句
// module.js
export default class Client{}
export const foo = ‘bar‘
// app.js
import Client, {foo} from ‘module.js‘
(4)导出一个模块的所有接口
Sybtax:export * from ‘module-name‘
// module.js
export function foo(){}
// app.js
export * from ‘module.js‘
(5)导出一个模块的部分接口
Sybtax:export {member} from ‘module-name‘
export {foo, bar} from ‘my_module‘;
// 等价于
import {foo, bar} from ‘my_moudle‘;
export {foo, bar};
(6)导出一个模块的默认接口
Sybtax:export {default} from ‘module‘
export {es6 ad default} from ‘my_module‘;
// 等价于
import {es6} from ‘my_module‘;
export default es6;
引入模块
(1)引入默认模块
import namespace from ‘module-name‘
import http from ‘http‘
(2)引入模块部分接口
import {member1, member2} from ‘module-name‘
import {isEmpty} from ‘lodash‘
(3)引入全部接口到指定命名空间
import * as namespace from ‘module-name‘
import * as lib from ‘module‘
(4)混入引入默认接口和命名接口
import {default as <default name>, method1} from ‘module-name‘
import {default as Client, utils} from ‘module‘
import <default name>, {<named modules>} from ‘module-name‘
import Client, {utils} from ‘module‘
(5)不引入接口,仅运行模块代码
import ‘module-name‘
ES6中提供的模块机制,可以“模块内容选择性引入”,其意味着可以通过Rollup和webpack2利用ES6模块化机制只压缩必要代码,最大程度地精简JavaScript引用的体积,避免了昂贵的网络带宽和较慢的页面加载速度。
总结:
写到这里,ES6的所有语法基本已全部描述,有彩蛋、也有单纯的语法糖。里面大多数的语法也可用通过ES5去shim(除了Proxy)。在Node6+以上,几乎所有的ES6语法被支持,前端可通过Babel6工具进行转换。在使用ES6过程中,有几个很爽的小特性,特整理如下:
设置对象变量键值的语法
ES6之前,不能在对象字面量里设置变量键值,必须要在对象初始化后增加键/值:
var myKey = ‘name‘;
var person = {
‘age‘: 25
};
person[myKey] = ‘ligang‘;
ES6中新增了[]方式,完美解决:
let myKey = ‘name‘;
let person = {
[myKey]: ‘ligang‘,
‘age‘: 25
};
模板字符串
ES6之前创建多行字符串必须使用\
作连接符。模板字符串的出现,让字符串拼接变得简单可维护。
let person = { name: ‘ligang‘, age: 26 };
console.log(`My name is ${person.name}.
My age is ${person.age}.`);
/* 结果:
My name is ligang.
My age is 26.
*/
### find/findIndex
JavaScript 提供了 Array.prototype.indexOf
方法,用来获取一个元素在数组中的索引,但是 indexOf
只能用来查找确切的值,不可以指定查询条件。find
和 findIndex
可以设置查询条件,在数组中查找到第一个满足条件的值。从而避免了循环处理!
let ages = [12, 19, 6, 4];
let firstAdult = ages.find(age => age >= 18); // 19
let firstAdultIndex = ages.findIndex(age => age >= 18); // 1
扩展运算符:…
扩展运算符表示一个数组或者一个可迭代对象可以在一次调用中将它们的内容分割为独立的参数。
// 传参给需要多个独立参数的函数 arguments
// 很像 Function.prototype.apply()
let numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1
// 将节点列表转换成数组
let divsArray = [...document.querySelectorAll(‘div‘)];
// 将参数转换成数组
let argsArray = [...arguments];
它能把可迭代对象(NodeList
, arguments
等等)转化为真正的数组,不再需要使用 Array.from
或Array.prototype.slice.call()
方法。
默认参数值
function greet(name = ‘ligang‘, callback = function(){}) {
console.log(`Hello ${name}!`);
// 不再需要条件判断“callback && typeof callback === ‘function‘”啦
callback();
}