(一) set 数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
(1) 基础知识
- set类似数组,成员的值唯一,没有重复值。
- Set 本身是一个构造函数,用来生成 Set 数据结构。
- Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
- 向 Set 加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值。
- Set 实例添加了两个NaN,但是只能加入一个。这表明,在 Set 内部,两个NaN是相等。
- set中两个对象总是不相等的。
- Array.from方法可以将 Set 结构转为数组。
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
----------------------------------------
var a = [1,2,2,3,4,5,2];
var s = new Set(a); // 注意这里得到的s是set结构,并不是数组,要得到数组可以用 [...s]
console.log(s)
// Set(5) {1, 2, 3, 4, 5}
-------------------------------------------
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); // 接受数组的对象作为参数。
items.size // 5
// 例三
function divs () {
return [...document.querySelectorAll('div')]; // 接受类似数组的对象作为参数。
}
const set = new Set(divs());
set.size // 56
// 类似于
divs().forEach(div => set.add(div));
set.size // 56
- 数组去重
// 去除数组的重复成员
[...new Set(array)]
- 对象,NaN在set数据结构中,两个对象不相等,NaN相等
var ss = new Set([{},{},NaN,NaN,2,2,3,4]);
console.log(ss)
// Set(6) {{…}, {…}, NaN, 2, 3, …}
(2) set实例的属性和方法
属性:
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
操作方法:------- ( 用于操作数据 )
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。
遍历方法:------- ( 用于遍历成员 )
keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员
- 需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
- 由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。
- Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。这意味着,可以省略values方法,直接用for...of循环遍历 Set。
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red
// green
// blue
Array.from方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
数组的map和filter方法也可以间接用于 Set
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
(二) for...of 和 for..in
for...of 用在数组中是数组的每个元素值
for...in 用在数组中是数组的每个元素值的 下标
for...of 可以用于Set结构 (变量就是values)
for...in 不能用在Set结构
https://www.cnblogs.com/enjoymylift/p/5997416.html
(三) map数据结构
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键 (对象中的 属性 或者说 键名 都是字符串)。这给它的使用带来了很大的限制。
(1) map数据结构类似于对象,键的范围比对象更强大
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content') // o是map结构的键,o是一个对象,不是字符串
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
// 上面代码:
// 使用 Map 结构的set方法,将对象o当作m的一个键,
// 然后又使用get方法读取这个键,
// 接着使用delete方法删除了这个键。
(2) 作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组
- Map作为构造函数,可以接受一个数组作为参数,数组的每个成员,是一个 表示 键值对的数组 (重要)
- 下面两个例子重要
(1)
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
// 上面代码在新建 Map 实例时,就指定了两个键name和title。
---------------------------------------------------------------------------------
(2)
Map构造函数接受数组作为参数,实际上执行的是下面的算法。
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
); -----------注意这种写法,forEach的参数是一个函数,函数的参数是一个数组------------
(3) 事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构,都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
- 这就是说,Set和Map都可以用来生成新的 Map。
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
- 如果对map结构 同一个键多次赋值,后面的值将覆盖前面的值。
const map = new Map();
map
.set(1, 'aaa')
.set(1, 'bbb');
map.get(1) // "bbb"
- 如果读取一个未知的键,则返回undefined。
- 注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
- Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。
这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
- 如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,
- 布尔值true和字符串true则是两个不同的键。
- undefined和null也是两个不同的键。
- 虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的
因此get方法无法读取该键,返回undefined。
---------------------------------------------------------------------
同理,同样的值的两个实例,在 Map 结构中被视为两个键。
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map
.set(k1, 111)
.set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
上面代码中,变量k1和k2的值是一样的,但是它们在 Map 结构中被视为两个键。
let map = new Map();
map.set(-0, 123);
map.get(+0) // 123
map.set(true, 1);
map.set('true', 2);
map.get(true) // 1
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3
map.set(NaN, 123);
map.get(NaN) // 123
(4) map数据结构的属性和方法
(1) size
(2) set
(3) get
(4) has
(5) delete
(6) clear
(7)遍历方法:
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历 Map 的所有成员。
- size属性
size属性返回 Map 结构的成员总数。
const map = new Map();
map.set(+0, 123);
map.set(true, 111);
map.set(NaN, '111222');
map.set(NaN, '555555'); // NaN在map结构中,是同一个键,后面的覆盖前面的, 所以size是3,而不是4
let bbb = map.size
console.log(bbb) // 3
- set (key, value) 方法 ---------------------- 返回整个 Map 结构
set方法返回的是当前的Map对象,因此可以采用链式写法。
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
get (key) 方法
get方法读取key对应的键值,如果找不到key,返回undefined。has (key) 方法
has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。delete (key) 属性
delete方法删除某个键,返回true。如果删除失败,返回false。
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
- clear() 方法
clear方法清除所有成员,没有返回值。
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
- 遍历方法
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。-----Map 结构的默认遍历器接口,就是entries方法。
forEach():遍历 Map 的所有成员。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) { // map.keys()
console.log(key);
}
// "F"
// "T"
// console.log( [...map.keys()] );
// ['F', 'T']
// console.log(map.keys()) -----------map.keys()返回的是map结构
// MapIterator {"F", "T"} -----------iterator是迭代器的意思
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) { ----------这里的map.entries()返回的map结构,item是数组
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) { ------------------和map.entries在forEach中得到的结果一样
console.log(key, value);
}
// "F" "no"
(5) map结构转换为数组结构
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
- 结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤(Map 本身没有map和filter方法)。
const map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
const map1 = new Map( // filter方法
[...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}
const map2 = new Map( // map方法
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
(6) Map和其他类型的数据转换
(1) Map转数组
const map = new Map()
.set(true, 7) // 可以采用链式写法
.set({foo: 3}, ['abc']);
map.set(['a'], 'a'); // map的键,不仅仅只是字符串
map.set({'b': 'b'}, 'b')
let c = [...map] // 用扩展运算符将map结构转换为数组结构
console.log(c)
除了用...展开运算符,还可以用Map数据结构的map方法:
map方法的函数参数分别是 value, key , map-----------map.forEach( (value, key, map) => {} )
--------和数组中的map方法不一样
--------数组中是array.forEach( (value, index, array) => {} )
--------数组中是array.forEach( ([a, b, c,...], index, array) => {} ) 多重数组时
const arr = [];
map.forEach( ( value, key, map ) => {
console.log(key,'key')
console.log(value,'value')
console.log(map,'map')
arr.push([key, value])
})
console.log(arr,'arr---------')
(2) 数组转换为Map
const items = [
[true, 7],
[{foo: 3}, ['abc']]
]
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
-----------------------------
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
(3) Map转化为对象
const map = new Map()
.set('yes', true)
.set('no', false)
const a = {}
for(let [key, value] of map) {
a[key] = value
}
console.log(a)
// Object {yes: true, no: false}
(4) 对象转化为Map
const obj = {a: 'yes', b: 'no'}
const map = new Map()
for( let i of Object.keys(obj) ) {
map.set(i, obj[i])
}
console.log(map)
(二) class类
(1) 概念
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。
另外,方法之间不需要逗号分隔,加了会报错。
类 的数据类型就是函数,类本身就指向构造函数。
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
- 类 使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。
class Bar {
doStuff() {
console.log('stuff');
}
}
var b = new Bar();
b.doStuff() // "stuff"
构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。
在类的实例上面调用方法,其实就是调用原型上的方法。
类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
类的属性名,可以采用表达式。
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
上面代码中,Square类的方法名getArea,是从表达式得到的。
(2) 严格模式
- 类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
(3) constructor 方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
- constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
- 一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
class Point {
}
// 等同于
class Point { // 在类中,如果没有显式定义constructor方法,则会自动添加一个空的constructor方法。
constructor() {}
}
- 类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
// ----toString不是类本身的属性,而是原型上的属性
point.__proto__.hasOwnProperty('toString') // true
(4) class表达式
函数一样,类也可以使用表达式的形式定义。
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
let inst = new MyClass();
inst.getClassName() // Me ---- 取得getClassName方法
Me.name // ReferenceError: Me is not defined ---- Me只在 Class 内部有定义。
上面代码使用表达式定义了一个类。
需要注意的是,这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类
(5) 类不存在变量提升(hoist)
new Foo(); // ReferenceError
class Foo {}
上面代码中,Foo类使用在前,定义在后,这样会报错
(6) class的静态方法 ( 重要 ) -----------------static------------------
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
- 如果在类的方法前,添加 static 关键字,表示该方法不会被实例继承,而是直接通过类来调用,称为“静态方法”
- 注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。( 重要 )
- 静态方法可以与非静态方法重名。
- 父类的静态方法,可以被子类继承。
- 静态方法也是可以从super对象上调用的。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello' // -----static静态方法,直接通过类来调用
var foo = new Foo();
foo.classMethod() // -----static静态方法,不被实例所继承
// TypeError: foo.classMethod is not a function
// 上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,
// 可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。
// 如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
- 注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。
静态方法可以与非静态方法重名。
class Foo {
static bar () {
this.baz(); // ---- 在类的 static 静态方法中,this指向类,而不是类的实例
}
static baz () { // ---- static静态方法不被实例所继承
console.log('hello');
}
baz () {
console.log('world');
}
}
Foo.bar() // hello // 所以输出 hello 而不是 world, 静态方法直接通过类来调用
const mm = new Foo();
mm.baz() // word // baz()函数前没有加static,该方法就可以被实例所继承
上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。
另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
- 父类的静态方法,可以被子类继承。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello' //---- 父类的静态方法可以被子类继承 !!!
- 静态方法也是可以从super对象上调用的。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
(7) class的静态属性 和 实例属性
(1) class的静态属性
静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
- Class 内部只有静态方法,没有静态属性。
- 类静态属性,和类的静态方法一样,父类的静态属性可以被子类所继承。
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
// 上面的写法 为Foo类定义了一个静态属性prop。
// 目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。
- 新的写法:
类的静态属性只要在上面的实例属性写法前面,加上static关键字就可以了
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
- 类的静态属性,新老写法对比
// 老写法
class Foo {
// ...
}
Foo.prop = 1;
// 新写法
class Foo {
static prop = 1;
}
(2) 类的实例属性
类的实例属性可以用等式,写入类的定义之中。
class MyClass {
myProp = 42; // 类的实例属性,在实例上可以读取这个属性
constructor() {
console.log(this.myProp); // 42
}
}
let mm = new MyClass ()
// ------注意这里一定要new MyClass(),new命令才能执行构造函数,不然构造函数没有调用
-------------------------------------------------------------------
class b {
name = 'wang' -------------------------------实例属性,实例可以读取
static c() {
this.d();
}
static d() {
console.log(
'class的static静态方法,不被实例所继承,直接通过类调用,
如果static静态方法中有this,那么this指向类,而不是指向实例'
)
}
d() {
console.log('该函数d 可以被实例所继承,因为不是static静态方法')
}
};
b.age = 20; ----- ----------------------------静态属性,子类可以继承
b.age // 20
const oo = new b()
console.log ( oo.name ) // wang ---------实例读取类中的实例属性
- 实例属性在react中的运用
以前的实例属性,只能在constructor中定义,现在可以在类中直接定义
以前的写法:
class ReactCounter extends React.Component {
constructor(props) {
super(props);
this.state = { // 在constructor中定义 实例属性 state
count: 0
};
}
}
---------------------------------------------------------
新的写法:可以不在constructor中定义了
class ReactCounter extends React.Component {
state = {
count: 0
};
}
(三) class的继承
Class 可以通过 extends 关键字实现继承
class Point {
}
class ColorPoint extends Point {
}
// 上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。
// 但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。
(1) super关键字
constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
// 上面代码中,constructor方法和toString方法之中,都出现了super关键字,
// 这里表示父类的构造函数,用来新建父类的this对象。
- 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
// 上面代码中,ColorPoint继承了父类Point,但是它的构造函数没有调用super方法,导致新建实例时报错。
- ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
- ES6 的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
- 如果子类没有定义constructor方法,这个方法会被默认添加,也就是说,不管有没有显式定义,任何一个子类都有constructor方法。
- 在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError -----在调用super()函数前,使用this会报错
super(x, y);
this.color = color; // 正确 -----在调用super()函数后,使用this正常
}
}
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true ------- cp 是否是 ColorPoint 的实例
cp instanceof Point // true ------- instance是实例的意思
Object.getPrototypeOf(ColorPoint) === Point
// true
------Object.getPrototypeOf(子类) === (父类) 值是true
// 因此 Object.getPrototypeOf() 可以用来从 子类 上获取 父类
// 同时,该方法也可以用来判断该子类是否继承自 某一个父类
(2) Object.getPrototypeOf()
Object.getPrototypeOf方法可以用来从子类上获取父类。
- 因此,可以使用这个方法判断,一个类是否继承了另一个类。
在上面的代码中已经分析了
Object.getPrototypeOf(ColorPoint) === Point ----表示 ColorPoint类 继承了 Point类
// true
(3) 再谈 super关键字
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
- super 可以当作函数使用
- super 可以当作对象使用
(1) super作为函数使用
super作为函数调用时,代表父类的构造函数,用来新建父类的this对象。ES6 要求,子类的构造函数必须执行一次super函数。
- 注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)。
- 作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
new.target指向当前正在执行的函数。
可以看到,在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。
也就是说,super()内部的this指向的是B。
- 作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。
class A {}
class B extends A {
m() {
super(); // 报错 ----super() 作为函数时,只能用在子类的构造函数当中
}
}
(2) super作为对象使用
super作为对象时:
- 在普通方法中,指向父类的原型对象;
- 在静态方法中,指向父类。
- 这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的
- ES6 规定,通过super调用父类的方法时,方法内部的this指向子类。
class A {
p() { // 类中定义的方法,实际上是定义在类的 prototype 原型对象 上的
return 2;
}
}
// A.prototype.p() -------结果是2,说明p()方法在A类的prototype上
class B extends A {
constructor() {
super(); // ---------super()最为函数,用在子类的constructor中,表示父类的构造函数
console.log(super.p()); // 2 // ----super作为对象,在普通函数中,指向父类的原型对象
}
}
let b = new B();
// 上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。
// 这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。
- 这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的
class A {
constructor() {
this.p = 2; // p 属性是生成在A类的实例上的,并不在A的prototype上
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
// 上面代码中,p是父类A实例的属性,super.p就引用不到它。
----------------------------------------------------------------
如果属性定义在父类的原型对象上,super就可以取到。
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
- ES6 规定,通过super调用父类的方法时,方法内部的this指向子类。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let b = new B();
b.m() // 2
// 上面代码中,super.print()虽然调用的是A.prototype.print()
// 但是A.prototype.print()内部的this指向子类B,导致输出的是2,而不是1。
// 也就是说,实际上执行的是super.print.call(this)。---------指向执行时所在的对象
(3) super作为对象,用在静态方法中
如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
- super对象,用在静态方法中,super指向父类,而不是父类的原型对象
- 重要
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) { // 静态方法,不能被实例使用,通过类直接调用,被子类所继承
super.myMethod(msg); // super对象用在静态方法中,指向父类,而不是父类的原型对象
}
myMethod(msg) { // super对象用在普通函数中,这里是实例函数,指向父类的原型对象
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1 // 通过 ( 类.方法 ) 调用的是静态方法
// 通过 Child类 直接调用的方法,是静态方法myMethod,super对象在静态方法中,super指向父类Parent,
// Parent.myMethod又是通过类直接调用的方法,是静态方法,所以输出:static 1
var child = new Child();
child.myMethod(2); // instance 2
(4) 注意,使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
class A {}
class B extends A {
constructor() {
super();
console.log(super); // 报错
}
}
(5) 类的 prototype 属性和__proto__
属性
(1)子类的__proto__
属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的__proto__
属性,表示方法的继承,总是指向父类的prototype属性。
class A {
}
class B extends A {
}
B.__proto__ === A // true
// 子类的__proto__属性,总是指向父类
B.prototype.__proto__ === A.prototype // true
// 子类的prototype属性的__proto__属性,总是指向 父类的prototype属性