ES6(2) map和set数据结构 + ( class类 ) + ( class的继承 )

(一) 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的参数是一个函数,函数的参数是一个数组------------


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属性
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 一:java概述: 1,JDK:Java Development Kit,java的开发和运行环境,java的开发...
    慕容小伟阅读 1,763评论 0 10
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,627评论 0 11
  • 001 专长还是全面? 专长,会让你在工作上更有竞争力,但却缺少变化的机会。而全面涉猎不同的知识,虽然短时间内,可...
    李不乖阅读 191评论 0 2
  • 前言:这是我在大学部门工作一年后的经历和感悟,纯属个人观点,不喜勿喷! 一年以前我大一,经过几轮痛苦的面试终于进入...
    OneNight壹夜阅读 399评论 0 0