ES6 新特性
简介:
- ECMAScript 6.0(以下简称 ES6)是 JavaScript 新一代标准,加入了很多新的功能和语法
- ECMAScript 和 JavaScript 的关系是:前者是后者的规格,后者是前者的一种实现
var let const
var
1. ES5 中作用域有:全局作用域、函数作用域。
// 全局作用域
var a = 123;
function test() {
console.log(a);
}
test(); // 123
//函数作用域
function test() {
var a = 123;
}
test();
console.log(a); // ReferenceError
// 不用var 全局定义
var z = 'n';
function test() {
z = 'z';
}
test();
console.log(z); // z
var z = 'n';
function test() {
var z = 'z';
}
test();
console.log(z); // n
2. var声明的变量会挂载在window上。容易引起变量污染。
// i只是用来控制循环, 但泄露为全局变量
for (var i = 0; i < 3; i++) { }
console.log(window.i); // 3
3. 变量提升
var a = 123;
function test() {
console.log(a); // a在声明前打印了
if(false) {
var a = 'abc'; // 变量提升,导致内层的 a 变量覆盖了外层的 a 变量
}
}
test(); // undefined
let
ES6 中新增了块级作用域概念。
1. 声明的变量只在块级作用域内有效
{
let i = 1;
{ let i = 2; }
}
if( ) { }
for( ) { }
2. 不存在变量提升(或者说存在变量提升,但不赋值),而是“绑定”在暂时性死区
var test = 1;
function func(){
console.log(test);
let test = 2;
};
func();
3. 不能重复声明
{
let a = 1;
let a = 2;
}
// 报错
{
let a = 10;
var a = 1;
}
//报错
const
1. const一旦声明变量,就必须立即初始化
const foo; // 报错
2. 只在声明所在的块级作用域内有效。
{
const MAX = 5;
}
console.log(MAX) //报错
3. 同样存在暂时性死区,只能在声明的位置后面使用。
{
console.log(MAX); // 报错
const MAX = 5;
}
4. 不能重复声明。
var message = 'Hello';
const message = 'Goodbye!';
let age = 25;
const age = 30;
5. const声明一个简单数据类型。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415
PI = 3; // 报错
6. const 定义的复合类型的数据可以进行修改:
const foo = { prop:1 };
foo.prop = 2; // 只改了数据结构,成功
foo = {}; // 改变了指针指向,报错
const a = [];
a.push('Hello'); // 不改动指针指向,只改了数据结构
a = ['lily']; // 改变了指针指向,报错
- 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量,const不允许更改
- 对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了
编程风格
- 建议不再使用var命令,let命令取代。两者语义相同,而且let没有副作用(全局污染,变量提升)
- 在let和const之间,建议优先使用const,一是const比较符合函数式编程思想,我不改变值,只新建值。二是JavaScript 编译器会对const进行优化,提高运行效率
- const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是万一修改了会报错
- 所有的函数都应该设置为常量。
二、解构赋值
- 解构:ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,
- 可以叫做“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
1. 数组的解构
let [a, b, c] = [1, 2, 3];
// 不完全解构:
let [x, y] = [1, 2, 3];
// 嵌套数组进行解构
let [foo, [[bar], baz]] = [1, [[2], 3]];
// 只取第三个
let [ , , third] = ['foo', 'bar', 'baz'];
// 中间的不取
let [x, , y] = [1, 2, 3];
// 取第一个 取剩余的
let [head, ...other] = [1, 2, 3, 4];
head
other
let [...other, last] = [1, 2, 3, 4]; // 报错:Rest element must be last element
2. 对象的解构
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
const { log, info, table, warn } = console;
3. 解构不成功系列:
let [foo] = [];
// foo: undefined
// 如果等号的右边 是不可遍历的结构
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
// 都将报错
// 不同名
let { abc } = { foo: 'aaa', bar: 'bbb' };
// abc: undefined
4. 用途:
// 交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
// 函数 return 数组 对象 进行解构
function getArr() {
return [1, 2, 3];
}
let [a, b, c] = getArr();
function getObj() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
// 传参:参数有次序的
function f([x, y, z]) { console.log(x,y,z) }
f([1, 2, 3]);
// 无序 也会对应着解构
function f({x, y, z}) { console.log(x,y,z) }
f({z: 3, y: 2, x: 1});
// 提取接口返回的 JSON 数据
const jsonData = {
id: 42,
status: 'OK',
data: [867, 5309]
};
const { data } = jsonData;
import React, { Component } from 'react';
class SomeComponent extends Component { ... }
编程风格
函数的参数如果是对象的成员,优先使用解构赋值。
const user = {
firstName:'firstName',
lastName:'lastName'
}
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
}
// good
function getFullName(obj) {
const { firstName, lastName } = obj;
}
// best
function getFullName({ firstName, lastName }) { }
getFullName(user);
三、模板字符串
传统的 JavaScript 语言,输出模板通常是这样写的。
const basket = {
count:123,
onSale:20
}
let str = 'There are ' + basket.count +
'items in your basket, ' +
+ basket.onSale +
' are on sale!';
上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题:
1. 反引号(`)标识 , 变量名写在 ${ } 之中。
const basket = {
count:123,
onSale:20
}
let str = `
There are ${basket.count} items
in your basket, ${basket.onSale}
are on sale!
`
2. 进行运算
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
3. 调用函数
function fn() {
return 'and';
}
`foo ${fn()} bar`
编程风格
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
四、函数的扩展
1. 默认值
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 对 0 false不友好
function log(y) {
y = y || '我是默认值';
console.log(y);
}
log('传值了');
log(0);
log(false);
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
// 例子1:
function log(y = '我是默认值') {
console.log(y);
}
log(0)
log(false)
// 例子2:
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }
2. 函数的length属性
- length:该函数预期传入的参数个数。
- 指定了默认值后,后面参数的length属性将失真。
// 默认值在不同位置的例子:
(function (a) {}).length // 没有默认值
(function (a = 5) {}).length
(function (a, b, c = 5) {}).length
(function(...args) {}).length // rest 参数
(function (a = 0, b, c) {}).length
(function (a, b = 1, c) {}).length
3. rest 参数
// arguments 能通过下标访问 转成数组
function sortNumbers() {
console.log(arguments)
return Array.prototype.slice.call(arguments);
}
sortNumbers(1,2,3)
// rest参数 真正的数组
const sortNumbers = (...numbers) => {
console.log(numbers)
}
sortNumbers(1,2,3)
4. 箭头函数
- ES6 允许使用“箭头”(=>)定义函数。
let f = () => 5;
// 等同于
let f = function () { return 5 };
let sum = (num1, num2) => num1 + num2;
// 等同于
let sum = function(num1, num2) {
return num1 + num2;
};
[1,2,3].map(function (x) {
return x * x;
});
// 等同于
[1,2,3].map(x => x * x);
const person = {
first:'first',
last:'last',
};
function full(person) {
return person.first + ' and ' + person.last;
}
full(person)
// 等同于
const full = ({ first, last }) => first + ' and ' + last;
full(person)
- 不能使用箭头函数的情况
// 定义对象方法的时候 不能用箭头函数
const cat = {
lives: 9,
jumps: () => {
console.log(this); // window
this.lives--;
}
}
cat.jumps(); // 箭头函数只能调用外层的this 把函数上下文绑定到了 window 上
// 解决方案:
const dog = {
lives: 9,
jumps() {
console.log(this === dog); // 写成普通函数 this就指向dog
this.lives--;
}
}
dog.jumps();
// 不可以当作构造函数。 因为构造函数需要有自己的this,箭头函数没有自己的this,用的是外层的this
let X = () => {};
x = new X();
// 定义原型方法的时候 不能用箭头函数
function Cat(name) {
this.name = name;
}
Cat.prototype.sayCatName = () => {
console.log(this); // window 箭头函数调用外层的this
return this.name;
};
const cat = new Cat('Mew');
cat.sayCatName(); // ''
// 定义事件回调函数 不能用箭头函数
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log(this === window); // true
this.innerHTML = 'Clicked button';
});
编程风格
- 使用匿名函数当作参数的场合
// bad
[1, 2, 3].map(function (x) {
return x * x;
});
// best
[1, 2, 3].map(x => x * x);
- 箭头函数取代Function.prototype.bind,不应再用 self/_this/that 绑定 this。
// bad
const self = this;
const boundMethod = function(...params) {
return method.apply(self, params);
}
// best
const boundMethod = (...params) => method.apply(this, params);
- 不要在函数体内使用 arguments 变量,使用 rest 运算符(...)代替。
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
- 函数默认值
// bad
function handleThings(opts) {
opts = opts || {};
}
// good
function handleThings(opts = {}) {
// ...
}
五、数组的扩展
1.扩展运算符
console.log(...[1, 2, 3])
console.log(1, ...[2, 3, 4], 5)
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
// 合并数组
function push(array, ...items) {
array.push(...items);
return array;
}
push([1,2,3]4,5,6)
// 函数参数展开
const arr = [0, 1, 2];
function f(x, y, z) {
console.log(x,y,z)
}
f(...arr);
// 合并数组
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5
arr1.concat(arr2, arr3);
// ES6
[...arr1, ...arr2, ...arr3]
//拆分字符串
[...'hello']
// [ 'h', 'e', 'l', 'l', 'o' ]
// 遍历map类型的keys
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
[...map.values()]
// Generator 返回数组
const go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1, 2, 3]
2. Array.from()
- 将对象转为真正的数组
- 伪数组:
- arguments对象
- document.querSelectAll('div') 返回的对象
- 字符串
// 伪数组的转换
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3,
};
console.log(arrayLike instanceof Array) // false;
let arr1 = [].slice.call(arrayLike); // ES5 转换成数组
let arr2 = Array.from(arrayLike); // ES6 转换成数组
// arguments对象
function foo() {
let args = Array.from(arguments);
console.log(arguments instanceof Array)
console.log(args instanceof Array)
}
foo(1,2,3);
// 字符串
Array.from('string');
- Array.from 第二个参数,作用类似于数组的map方法
let arr = [2,3,4];
Array.from(arr, x => x * x);
// 等同于
Array.from(arr).map(x => x * x);
3. 数组实例的 includes()
// ES5 判读是否存在与数组
[1,2,3,NaN].indexOf(1) !== -1 ? console.log('存在') : console.log('不存在');
[1,2,3,NaN].indexOf(4) !== -1 ? console.log('存在') : console.log('不存在');
// 误判 缺点:1. 不够语义化 2. 它内部使用严格相等运算符(===)进行判断
[1,2,3,NaN].indexOf(NaN) !== -1 ? console.log('存在') : console.log('不存在');
// ES6:
[1, 2, 3].includes(1) // true
[1, 2, 3].includes(4) // false
[1, 2, 3, NaN].includes(NaN) // true
六.对象的扩展
1. Object.assign()
- 对象的合并
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
- 自动转换成对象
typeof Object.assign(2) // 'object'
Object.assign(undefined) // 报错
Object.assign(null) // 报错 undefined和null无法转成对象
- 属于浅拷贝 或者说是一级深拷贝
const obj1 = {
a: { b: 1 },
c:2
};
const obj2 = Object.assign({}, obj1); // 与 {} 拼接会产生一个新的对象
obj1.c = 3;
obj2 // c不会变 一级深拷贝
obj1.a.b = 2;
obj2 // b会跟着变 并没有开辟新地址
- 数组的处理
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
- 用途:
// ES5写法 prototype 添加方法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
// ES6 prototype 添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
2. Object.keys(),Object.values(),Object.entries()
- 拿对象的keys values 键值对
let obj1 = { foo: 'bar', baz: 42 };
Object.keys(obj1)
let obj2 = { foo: 'bar', baz: 42 };
Object.values(obj2)
let obj3 = { foo: 'bar', baz: 42 };
Object.entries(obj3)
- 与for of结合
let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };
// 拿obj的keys
for (let key of keys(obj)) {
console.log(key);
}
// 拿obj的values
for (let value of values(obj)) {
console.log(value);
}
for (let [key, value] of entries(obj)) {
console.log(key, value);
}
3. Object.fromEntries()
- Object.entries()的逆操作,用于将一个键值对数组转为对象。
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: 'bar', baz: 42 }
七. Promise
1. 简介
-
Promise 是异步编程的一种解决方案
-
有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。 只有异步操作的结果,可以决定当前是哪一种状态。
-
缺点:
无法取消Promise,一旦新建它就会立即执行,无法中途取消。
其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 -
Promise对象是一个构造函数,创造Promise实例
-
resolve: “未完成”变为“成功”
-
reject: “未完成”变为“失败”
-
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
return resolve(value);
} else {
return reject(error);
}
});
2. then()
- 第一个回调函数 状态变为resolved时调用
- 第二个回调函数 状态变为rejected时调用
promise.then(function(value) {
// success resolved时调用
}, function(error) {
// failure rejected时调用
});
拿ajax举例:
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = handler;
client.responseType = 'json';
client.setRequestHeader('Accept', 'application/json');
client.send();
});
return promise;
};
// 一个异步
getJSON('/posts.json').then(
json => console.log(json),
err => console.error('出错了', err)
);
// 嵌套异步
getJSON('/post/1.json').then(
post => getJSON(post.commentURL) // 返回的还是一个Promise对象(即有异步操作)
).then(
comments => console.log('resolved: ', comments), // 等待前一个Promise对象的状态发生变化,才会被调
err => console.log('rejected: ', err)
);
3. catch()
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good 可以捕获then执行中的错误,也更接近同步的写法(try/catch)
promise
.then(function(data) {
// success
})
.catch(function(err) {
// error
});
4. Promise.all()
- 6 个 Promise 实例, 6个都成功或1个失败 才会走回调
- 6个都成功:返回值组成一个数组给回调
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + '.json');
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
八. Iterator(遍历器)
- 遍历器(Iterator):是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作
- ES6新增:Map Set 结构
let m = new Map();
m.set('numberA', 67);
m.set('numberB', 88);
m.get('numberA');
let s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
-
ES6 新增遍历命令for...of循环,Iterator 接口主要供for...of消费, Set 和 Map 结构、arguments对象、DOM NodeList 对象、Generator 对象,以及字符串。
-
任何数据结构只要部署 Iterator 接口
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
- 原生具备 Iterator 接口的数据结构:
Array, Map, Set, String, 函数的arguments对象, NodeList对象
// Array
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
// Set
const engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
let generator = function* () {
yield 1;
yield 2;
yield 5;
};
var iterator = generator();
for(let v of iterator) {
console.log(v);
}
九. Generator
1. 简介
- Generator 也是异步编程的一种解决方案
- function关键字与函数名之间有一个星号
- 函数体内部使用yield表达式,暂停执行的标记
- Generator函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,我们可以通过调用 next 方法,使得指针移向下一个状态
2.语法
- *的位置
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
let obj = {
* myGeneratorMethod() {
···
}
};
// 等价
let obj = {
myGeneratorMethod: function* () {
// ···
}
};
- 在另一个表达式之中,必须放在圆括号里面。
- 函数参数或放在赋值表达式的右边,可以不加括号。
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
3. 方法属性
- yield:暂停
- next:执行
- value:是yield表达式的值
- done:表示遍历是否结束。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
//' { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
3. next()
- next的参数:设置成上一个yield表达式的返回值
- next的输出:会执行一条yield语句,并返回yield后面的值
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
let b = foo(5);
b.next() // { value:6, done:false } x=5, 执行第一行,输出yield的值 x+1的值6
b.next(12) // { value:8, done:false } y=24,执行第二行,输出yield的值 将上一次yield表达式的值设为12 24/3=8
b.next(13) // { value:42, done:true } z=13 执行第三行, return (x + y + z);
4. throw()、return()
function* gen(x,y){
yield 1;
yield 2;
yield 3;
}
let g = gen();
g.next(); //{value: 1, done: false}
g.return(5); //{value: 5, done: true}
g.next(); //{value: undefined, done: true}
function* foo(x,y){
yield 1;
yield 2;
yield 3;
}
let f = foo();
f.next(); //{value: 1, done: false}
f.throw(new Error('出错了')); // Error
f.next(); //{value: undefined, done: true}
5. 异步对比
// 传统回调函数
ajax('url_1', () => {
// callback 函数体
ajax('url_2', () => {
// callback 函数体
ajax('url_3', () => {
// callback 函数体
})
})
})
// 无法取消 Promise ,错误需要通过回调函数来捕获
ajax('url_1')
.then(res => {
// 操作逻辑
return ajax('url_2')
}).then(res => {
// 操作逻辑
return ajax('url_3')
}).then(res => {
// 操作逻辑
})
// Generator
function *fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
// async/await
async function test() {
let result1 = await fetch('XXX1')
let result2 = await fetch('XXX2')
let result3 = await fetch('XXX3')
}
十. class
1. 构造函数 与 class
//ES5 生成实例对象的传统方法
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return `${this.x},${this.y}`
};
let p = new Point(1, 2);
// ES6 class
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `${this.x},${this.y}`
}
}
//其实就是调用原型上的方法。
// 等同于
Point.prototype = {
constructor() {},
toString() {}
};
2. new类的实例
- 定义在this上的属性才是属于实例的本身,否则都是定义在其原型上
- x,y绑定在point上,toString绑定在Point上
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
this.toString = this.toString.bind(this);
}
toString() { // 普通函数需绑定this
return `${this.x},${this.y}`
}
//toString = () => { // 箭头函数不需要绑定this 引用外层this
// return `${this.x},${this.y}`
//}
}
let point = new Point(2, 3);
let { toString } = point;
toString(); // (2, 3)
// 检测自身属性中是否具有指定的属性
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.constructor === Point; // true
point.__proto__.hasOwnProperty('toString') // true 方法在point的原型Point上
3. 继承
- ES5继承:通过借用构造函数来继承属性, 通过原型链来继承方法
- 先创造子类的实例对象this,然后再将父类的方法属性用call方法添加到this上面 。
function Father(name, age){
this.name = name;
this.age = age;
}
function Son(name, age){
Father.call(this, name, age);
}
Son.prototype = Object.create(Father.prototype); // 通过Object.crete()去关联两者的prototype
Son.prototype.constructor = Son; // 将 Son 的constructor指向自己
Son.prototype.show = function () {
console.log(this.name,this.age);
};
var s = new Son('子类', 110);
s.show();
- ES6继承
// class构造一个父类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
p() {
return 2;
}
}
// extends,super关键字 继承所有的属性,方法
class ColorPoint extends Point { // 先生成父类实例
constructor(x, y) { // 调用子类的构造函数修饰父类实例
super(x, y); // super当函数用
console.log(x,y);
console.log(super.p()); // super当对象用
}
}
let colorPoint = new ColorPoint('xx','yy');
// 如何判断一个类是否继承了另一个类
Object.getPrototypeOf(ColorPoint) === Point;
结束!
网友评论