当前位置: 首页 > news >正文

义乌网站建设方式栖霞建设招标网站

义乌网站建设方式,栖霞建设招标网站,采购网上商城,wordpress 嵌入html5个人主页#xff1a;学习前端的小z 个人专栏#xff1a;JavaScript 精粹 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结#xff0c;欢迎大家在评论区交流讨论#xff01; ES5、ES6介绍 文章目录 #x1f4af;Class#x1f35f;1 类的由来#x1f35f;2 co… 个人主页学习前端的小z 个人专栏JavaScript 精粹 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结欢迎大家在评论区交流讨论 ES5、ES6介绍 文章目录 Class1 类的由来2 constructor 方法3 类的实例4 取值函数getter和存值函数setter5 属性表达式6 Class 表达式7 注意点8 静态方法9 实例属性的新写法10 静态属性 11 私有方法和私有属性11.1 现有的解决方案11.2 私有属性的提案11.3 new.target 属性 Class extends1 Object.getPrototypeOf()2 super 关键字3 类的 prototype 属性和__proto__属性3.1 实例的 __proto__ 属性 4 原生构造函数的继承5 Mixin 模式的实现 Class 1 类的由来 JavaScript 语言中生成实例对象的传统方法是通过构造函数。下面是一个例子。 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);上面这种写法跟传统的面向对象语言比如 C 和 Java差异很大很容易让新学习这门语言的程序员感到困惑。 ES6 提供了更接近传统语言的写法引入了 Class类这个概念作为对象的模板。通过class关键字可以定义类。 基本上ES6 的class可以看作只是一个语法糖它的绝大部分功能ES5 都可以做到新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写就是下面这样。 class Point {constructor(x, y) {this.x x;this.y y;}toString() {return ( this.x , this.y );} }上面代码定义了一个“类”可以看到里面有一个constructor方法这就是构造方法而this关键字则代表实例对象。也就是说ES5 的构造函数Point对应 ES6 的Point类的构造方法。 Point类除了构造方法还定义了一个toString方法。注意定义“类”的方法的时候前面不需要加上function这个关键字直接把函数定义放进去了就可以了。另外方法之间不需要逗号分隔加了会报错。 ES6 的类完全可以看作构造函数的另一种写法。 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属性上面。 class Point {constructor() {// ...}toString() {// ...}toValue() {// ...} }// 等同于Point.prototype {constructor() {},toString() {},toValue() {}, };在类的实例上面调用方法其实就是调用原型上的方法。 class B {} let b new B();b.constructor B.prototype.constructor // true上面代码中b是B类的实例它的constructor方法就是B类原型的constructor方法。 由于类的方法都定义在prototype对象上面所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。 class Point {constructor(){// ...} }Object.assign(Point.prototype, {toString(){},toValue(){} });prototype对象的constructor属性直接指向“类”的本身这与 ES5 的行为是一致的。 Point.prototype.constructor Point // true另外类的内部所有定义的方法都是不可枚举的non-enumerable。 class Point {constructor(x, y) {// ...}toString() {// ...} }Object.keys(Point.prototype) // [] Object.getOwnPropertyNames(Point.prototype) // [constructor,toString]上面代码中toString方法是Point类内部定义的方法它是不可枚举的。这一点与 ES5 的行为不一致。 var Point function (x, y) {// ... };Point.prototype.toString function() {// ... };Object.keys(Point.prototype) // [toString] Object.getOwnPropertyNames(Point.prototype) // [constructor,toString]上面代码采用 ES5 的写法toString方法就是可枚举的。 2 constructor 方法 constructor方法是类的默认方法通过new命令生成对象实例时自动调用该方法。一个类必须有constructor方法如果没有显式定义一个空的constructor方法会被默认添加。 class Point { }// 等同于 class Point {constructor() {} }上面代码中定义了一个空的类PointJavaScript 引擎会自动为它添加一个空的constructor方法。 constructor方法默认返回实例对象即this完全可以指定返回另外一个对象。 class Foo {constructor() {return Object.create(null);} }new Foo() instanceof Foo // false上面代码中constructor函数返回一个全新的对象结果导致实例对象不是Foo类的实例。 类必须使用new调用否则会报错。这是它跟普通构造函数的一个主要区别后者不用new也可以执行。 class Foo {constructor() {return Object.create(null);} }Foo() // TypeError: Class constructor Foo cannot be invoked without new3 类的实例 生成类的实例的写法与 ES5 完全一样也是使用new命令。前面说过如果忘记加上new像函数那样调用Class将会报错。 class Point {// ... }// 报错 var point Point(2, 3);// 正确 var point new Point(2, 3);与 ES5 一样实例的属性除非显式定义在其本身即定义在this对象上否则都是定义在原型上即定义在class上。 //定义类 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 point.__proto__.hasOwnProperty(toString) // true上面代码中x和y都是实例对象point自身的属性因为定义在this变量上所以hasOwnProperty方法返回true而toString是原型对象的属性因为定义在Point类上所以hasOwnProperty方法返回false。这些都与 ES5 的行为保持一致。 与 ES5 一样类的所有实例共享一个原型对象。 var p1 new Point(2,3); var p2 new Point(3,2);p1.__proto__ p2.__proto__ //true上面代码中p1和p2都是Point的实例它们的原型都是Point.prototype所以__proto__属性是相等的。 这也意味着可以通过实例的__proto__属性为“类”添加方法。 __proto__ 并不是语言本身的特性这是各大厂商具体实现时添加的私有属性虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性但依旧不建议在生产中使用该属性避免对环境产生依赖。生产环境中我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型然后再来为原型添加方法/属性。 var p1 new Point(2,3); var p2 new Point(3,2);p1.__proto__.printName function () { return Oops };p1.printName() // Oops p2.printName() // Oopsvar p3 new Point(4,2); p3.printName() // Oops上面代码在p1的原型上添加了一个printName方法由于p1的原型就是p2的原型因此p2也可以调用这个方法。而且此后新建的实例p3也可以调用这个方法。这意味着使用实例的__proto__属性改写原型必须相当谨慎不推荐使用因为这会改变“类”的原始定义影响到所有实例。 4 取值函数getter和存值函数setter 与 ES5 一样在“类”的内部可以使用get和set关键字对某个属性设置存值函数和取值函数拦截该属性的存取行为。 class MyClass {constructor() {// ...}get prop() {return getter;}set prop(value) {console.log(setter: value);} }let inst new MyClass();inst.prop 123; // setter: 123inst.prop // getter上面代码中prop属性有对应的存值函数和取值函数因此赋值和读取行为都被自定义了。 存值函数和取值函数是设置在属性的 Descriptor 对象上的。 class CustomHTMLElement {constructor(element) {this.element element;}get html() {return this.element.innerHTML;}set html(value) {this.element.innerHTML value;} }var descriptor Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, html );get in descriptor // true set in descriptor // true上面代码中存值函数和取值函数是定义在html属性的描述对象上面这与 ES5 完全一致。 5 属性表达式 类的属性名可以采用表达式。 let methodName getArea;class Square {constructor(length) {// ...}[methodName]() {// ...} }上面代码中Square类的方法名getArea是从表达式得到的。 6 Class 表达式 与函数一样类也可以使用表达式的形式定义。 const MyClass class Me {getClassName() {return Me.name;} };上面代码使用表达式定义了一个类。需要注意的是这个类的名字是Me但是Me只在 Class 的内部可用指代当前类。在 Class 外部这个类只能用MyClass引用。 let inst new MyClass(); inst.getClassName() // Me Me.name // ReferenceError: Me is not defined上面代码表示Me只在 Class 内部有定义。 如果类的内部没用到的话可以省略Me也就是可以写成下面的形式。 const MyClass class { /* ... */ };采用 Class 表达式可以写出立即执行的 Class。 let person new class {constructor(name) {this.name name;}sayName() {console.log(this.name);} }(张三);person.sayName(); // 张三上面代码中person是一个立即执行的类的实例。 7 注意点 1严格模式 类和模块的内部默认就是严格模式所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中就只有严格模式可用。考虑到未来所有的代码其实都是运行在模块之中所以 ES6 实际上把整个语言升级到了严格模式。 2不存在提升 类不存在变量提升hoist这一点与 ES5 完全不同。 new Foo(); // ReferenceError class Foo {}上面代码中Foo类使用在前定义在后这样会报错因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关必须保证子类在父类之后定义。 {let Foo class {};class Bar extends Foo {} }上面的代码不会报错因为Bar继承Foo的时候Foo已经有定义了。但是如果存在class的提升上面代码就会报错因为class会被提升到代码头部而let命令是不提升的所以导致Bar继承Foo的时候Foo还没有定义。 3name 属性 由于本质上ES6 的类只是 ES5 的构造函数的一层包装所以函数的许多特性都被Class继承包括name属性。 class Point {} Point.name // Pointname属性总是返回紧跟在class关键字后面的类名。 4Generator 方法 如果某个方法之前加上星号*就表示该方法是一个 Generator 函数。 class Foo {constructor(...args) {this.args args;}* [Symbol.iterator]() {for (let arg of this.args) {yield arg;}} }for (let x of new Foo(hello, world)) {console.log(x); } // hello // world上面代码中Foo类的Symbol.iterator方法前有一个星号表示该方法是一个 Generator 函数。Symbol.iterator方法返回一个Foo类的默认遍历器for...of循环会自动调用这个遍历器。 5this 的指向 类的方法内部如果含有this它默认指向类的实例。但是必须非常小心一旦单独使用该方法很可能报错。 class Logger {printName(name there) {this.print(Hello ${name});}print(text) {console.log(text);} }const logger new Logger(); const { printName } logger; printName(); // TypeError: Cannot read property print of undefined上面代码中printName方法中的this默认指向Logger类的实例。但是如果将这个方法提取出来单独使用this会指向该方法运行时所在的环境由于 class 内部是严格模式所以 this 实际指向的是undefined从而导致找不到print方法而报错。 一个比较简单的解决方法是在构造方法中绑定this这样就不会找不到print方法了。 class Logger {constructor() {this.printName this.printName.bind(this);}// ... }另一种解决方法是使用箭头函数。 class Obj {constructor() {this.getThis () this;} }const myObj new Obj(); myObj.getThis() myObj // true箭头函数内部的this总是指向定义时所在的对象。上面代码中箭头函数位于构造函数内部它的定义生效的时候是在构造函数执行的时候。这时箭头函数所在的运行环境肯定是实例对象所以this会总是指向实例对象。 还有一种解决方法是使用Proxy获取方法的时候自动绑定this。 function selfish (target) {const cache new WeakMap();const handler {get (target, key) {const value Reflect.get(target, key);if (typeof value ! function) {return value;}if (!cache.has(value)) {cache.set(value, value.bind(target));}return cache.get(value);}};const proxy new Proxy(target, handler);return proxy; }const logger selfish(new Logger());8 静态方法 类相当于实例的原型所有在类中定义的方法都会被实例继承。如果在一个方法前加上static关键字就表示该方法不会被实例继承而是直接通过类来调用这就称为“静态方法”。 class Foo {static classMethod() {return hello;} }Foo.classMethod() // hellovar foo new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function上面代码中Foo类的classMethod方法前有static关键字表明该方法是一个静态方法可以直接在Foo类上调用Foo.classMethod()而不是在Foo类的实例上调用。如果在实例上调用静态方法会抛出一个错误表示不存在该方法。 注意如果静态方法包含this关键字这个this指的是类而不是实例。 class Foo {static bar() {this.baz();}static baz() {console.log(hello);}baz() {console.log(world);} }Foo.bar() // hello上面代码中静态方法bar调用了this.baz这里的this指的是Foo类而不是Foo的实例等同于调用Foo.baz。另外从这个例子还可以看出静态方法可以与非静态方法重名。 父类的静态方法可以被子类继承。 class Foo {static classMethod() {return hello;} }class Bar extends Foo { }Bar.classMethod() // hello上面代码中父类Foo有一个静态方法子类Bar可以调用这个方法。 静态方法也是可以从super对象上调用的。 class Foo {static classMethod() {return hello;} }class Bar extends Foo {static classMethod() {return super.classMethod() , too;} }Bar.classMethod() // hello, too9 实例属性的新写法 实例属性除了定义在constructor()方法里面的this上面也可以定义在类的最顶层。 class IncreasingCounter {constructor() {this._count 0;}get value() {console.log(Getting the current value!);return this._count;}increment() {this._count;} }上面代码中实例属性this._count定义在constructor()方法里面。另一种写法是这个属性也可以定义在类的最顶层其他都不变。 class IncreasingCounter {_count 0;get value() {console.log(Getting the current value!);return this._count;}increment() {this._count;} }上面代码中实例属性_count与取值函数value()和increment()方法处于同一个层级。这时不需要在实例属性前面加上this。 这种新写法的好处是所有实例对象自身的属性都定义在类的头部看上去比较整齐一眼就能看出这个类有哪些实例属性。 class foo {bar hello;baz world;constructor() {// ...} }上面的代码一眼就能看出foo类有两个实例属性一目了然。另外写起来也比较简洁。 10 静态属性 静态属性指的是 Class 本身的属性即Class.propName而不是定义在实例对象this上的属性。 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; }上面代码中老写法的静态属性定义在类的外部。整个类生成以后再生成静态属性。这样让人很容易忽略这个静态属性也不符合相关代码应该放在一起的代码组织原则。另外新写法是显式声明declarative而不是赋值处理语义更好。 11 私有方法和私有属性 11.1 现有的解决方案 私有方法和私有属性是只能在类的内部访问的方法和属性外部不能访问。这是常见需求有利于代码的封装但 ES6 不提供只能通过变通方法模拟实现。 一种做法是在命名上加以区别。 class Widget {// 公有方法foo (baz) {this._bar(baz);}// 私有方法_bar(baz) {return this.snaf baz;}// ... }上面代码中_bar方法前面的下划线表示这是一个只限于内部使用的私有方法。但是这种命名是不保险的在类的外部还是可以调用到这个方法。 另一种方法就是索性将私有方法移出模块因为模块内部的所有方法都是对外可见的。 class Widget {foo (baz) {bar.call(this, baz);}// ... }function bar(baz) {return this.snaf baz; }上面代码中foo是公开方法内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。 还有一种方法是利用Symbol值的唯一性将私有方法的名字命名为一个Symbol值。 const bar Symbol(bar); const snaf Symbol(snaf);export default class myClass{// 公有方法foo(baz) {this[bar](baz);}// 私有方法[bar](baz) {return this[snaf] baz;}// ... };上面代码中bar和snaf都是Symbol值一般情况下无法获取到它们因此达到了私有方法和私有属性的效果。但是也不是绝对不行Reflect.ownKeys()依然可以拿到它们。 const inst new myClass();Reflect.ownKeys(myClass.prototype) // [ constructor, foo, Symbol(bar) ]上面代码中Symbol 值的属性名依然可以从类的外部拿到。 11.2 私有属性的提案 目前有一个提案为class加了私有属性。方法是在属性名之前使用#表示。 class IncreasingCounter {#count 0;get value() {console.log(Getting the current value!);return this.#count;}increment() {this.#count;} }上面代码中#count就是私有属性只能在类的内部使用this.#count。如果在类的外部使用就会报错。 const counter new IncreasingCounter(); counter.#count // 报错 counter.#count 42 // 报错上面代码在类的外部读取私有属性就会报错。 下面是另一个例子。 class Point {#x;constructor(x 0) {this.#x x;}get x() {return this.#x;}set x(value) {this.#x value;} }上面代码中#x就是私有属性在Point类之外是读取不到这个属性的。由于井号#是属性名的一部分使用时必须带有#一起使用所以#x和x是两个不同的属性。 之所以要引入一个新的前缀#表示私有属性而没有采用private关键字是因为 JavaScript 是一门动态语言没有类型声明使用独立的符号似乎是唯一的比较方便可靠的方法能够准确地区分一种属性是否为私有属性。另外Ruby 语言使用表示私有属性ES6 没有用这个符号而使用#是因为已经被留给了 Decorator。 这种写法不仅可以写私有属性还可以用来写私有方法。 class Foo {#a;#b;constructor(a, b) {this.#a a;this.#b b;}#sum() {return #a #b;}printSum() {console.log(this.#sum());} }上面代码中#sum()就是一个私有方法。 另外私有属性也可以设置 getter 和 setter 方法。 class Counter {#xValue 0;constructor() {super();// ...}get #x() { return #xValue; }set #x(value) {this.#xValue value;} }上面代码中#x是一个私有属性它的读写都通过get #x()和set #x()来完成。 私有属性不限于从this引用只要是在类的内部实例也可以引用私有属性。 class Foo {#privateValue 42;static getPrivateValue(foo) {return foo.#privateValue;} }Foo.getPrivateValue(new Foo()); // 42上面代码允许从实例foo上面引用私有属性。 私有属性和私有方法前面也可以加上static关键字表示这是一个静态的私有属性或私有方法。 class FakeMath {static PI 22 / 7;static #totallyRandomNumber 4;static #computeRandomNumber() {return FakeMath.#totallyRandomNumber;}static random() {console.log(I heard you like random numbers…)return FakeMath.#computeRandomNumber();} }FakeMath.PI // 3.142857142857143 FakeMath.random() // I heard you like random numbers… // 4 FakeMath.#totallyRandomNumber // 报错 FakeMath.#computeRandomNumber() // 报错上面代码中#totallyRandomNumber是私有属性#computeRandomNumber()是私有方法只能在FakeMath这个类的内部调用外部调用就会报错。 11.3 new.target 属性 new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性该属性一般用在构造函数之中返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的new.target会返回undefined因此这个属性可以用来确定构造函数是怎么调用的。 function Person(name) {if (new.target ! undefined) {this.name name;} else {throw new Error(必须使用 new 命令生成实例);} }// 另一种写法 function Person(name) {if (new.target Person) {this.name name;} else {throw new Error(必须使用 new 命令生成实例);} }var person new Person(张三); // 正确 var notAPerson Person.call(person, 张三); // 报错上面代码确保构造函数只能通过new命令调用。 Class 内部调用new.target返回当前 Class。 class Rectangle {constructor(length, width) {console.log(new.target Rectangle);this.length length;this.width width;} }var obj new Rectangle(3, 4); // 输出 true需要注意的是子类继承父类时new.target会返回子类。 class Rectangle {constructor(length, width) {console.log(new.target Rectangle);// ...} }class Square extends Rectangle {constructor(length, width) {super(length, width);} }var obj new Square(3); // 输出 false上面代码中new.target会返回子类。 利用这个特点可以写出不能独立使用、必须继承后才能使用的类。 class Shape {constructor() {if (new.target Shape) {throw new Error(本类不能实例化);}} }class Rectangle extends Shape {constructor(length, width) {super();// ...} }var x new Shape(); // 报错 var y new Rectangle(3, 4); // 正确上面代码中Shape类不能被实例化只能用于继承。 注意在函数外部使用new.target会报错。 Class extends Class 可以通过extends关键字实现继承这比 ES5 的通过修改原型链实现继承要清晰和方便很多。 class Point { }class ColorPoint extends Point { }上面代码定义了一个ColorPoint类该类通过extends关键字继承了Point类的所有属性和方法。但是由于没有部署任何代码所以这两个类完全一样等于复制了一个Point类。下面我们在ColorPoint内部加上代码。 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对象必须先通过父类的构造函数完成塑造得到与父类同样的实例属性和方法然后再对其进行加工加上子类自己的实例属性和方法。如果不调用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方法。 class ColorPoint extends Point { }// 等同于 class ColorPoint extends Point {constructor(...args) {super(...args);} }另一个需要注意的地方是在子类的构造函数中只有调用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; // ReferenceErrorsuper(x, y);this.color color; // 正确} }上面代码中子类的constructor方法没有调用super之前就使用this关键字结果报错而放在super方法之后就是正确的。 下面是生成子类实例的代码。 let cp new ColorPoint(25, 8, green);cp instanceof ColorPoint // true cp instanceof Point // true上面代码中实例对象cp同时是ColorPoint和Point两个类的实例这与 ES5 的行为完全一致。 最后父类的静态方法也会被子类继承。 class A {static hello() {console.log(hello world);} }class B extends A { }B.hello() // hello world上面代码中hello()是A类的静态方法B继承A也继承了A的静态方法。 1 Object.getPrototypeOf() Object.getPrototypeOf方法可以用来从子类上获取父类。 Object.getPrototypeOf(ColorPoint) Point // true因此可以使用这个方法判断一个类是否继承了另一个类。 2 super 关键字 super这个关键字既可以当作函数使用也可以当作对象使用。在这两种情况下它的用法完全不同。 第一种情况super作为函数调用时代表父类的构造函数。ES6 要求子类的构造函数必须执行一次super函数。 class A {}class B extends A {constructor() {super();} }上面代码中子类B的构造函数之中的super()代表调用父类的构造函数。这是必须的否则 JavaScript 引擎会报错。 注意super虽然代表了父类A的构造函数但是返回的是子类B的实例即super内部的this指的是B的实例因此super()在这里相当于A.prototype.constructor.call(this)。 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()用在B类的m方法之中就会造成语法错误。 第二种情况super作为对象时在普通方法中指向父类的原型对象在静态方法中指向父类。 class A {p() {return 2;} }class B extends A {constructor() {super();console.log(super.p()); // 2} }let b new B();上面代码中子类B当中的super.p()就是将super当作一个对象使用。这时super在普通方法之中指向A.prototype所以super.p()就相当于A.prototype.p()。 这里需要注意由于super指向父类的原型对象所以定义在父类实例上的方法或属性是无法通过super调用的。 class A {constructor() {this.p 2;} }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();上面代码中属性x是定义在A.prototype上面的所以super.x可以取到它的值。 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)。 由于this指向子类实例所以如果通过super对某个属性赋值这时super就是this赋值的属性会变成子类实例的属性。 class A {constructor() {this.x 1;} }class B extends A {constructor() {super();this.x 2;super.x 3;console.log(super.x); // undefinedconsole.log(this.x); // 3} }let b new B();上面代码中super.x赋值为3这时等同于对this.x赋值为3。而当读取super.x的时候读的是A.prototype.x所以返回undefined。 如果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);}myMethod(msg) {super.myMethod(msg);} }Child.myMethod(1); // static 1var child new Child(); child.myMethod(2); // instance 2上面代码中super在静态方法之中指向父类在普通方法之中指向父类的原型对象。 另外在子类的静态方法中通过super调用父类的方法时方法内部的this指向当前的子类而不是子类的实例。 class A {constructor() {this.x 1;}static print() {console.log(this.x);} }class B extends A {constructor() {super();this.x 2;}static m() {super.print();} }B.x 3; B.m() // 3上面代码中静态方法B.m里面super.print指向父类的静态方法。这个方法里面的this指向的是B而不是B的实例。 注意使用super的时候必须显式指定是作为函数、还是作为对象使用否则会报错。 class A {}class B extends A {constructor() {super();console.log(super); // 报错} }上面代码中console.log(super)当中的super无法看出是作为函数使用还是作为对象使用所以 JavaScript 引擎解析代码的时候就会报错。这时如果能清晰地表明super的数据类型就不会报错。 class A {}class B extends A {constructor() {super();console.log(super.valueOf() instanceof B); // true} }let b new B();上面代码中super.valueOf()表明super是一个对象因此就不会报错。同时由于super使得this指向B的实例所以super.valueOf()返回的是一个B的实例。 最后由于对象总是继承其他对象的所以可以在任意一个对象中使用super关键字。 var obj {toString() {return MyObject: super.toString();} };obj.toString(); // MyObject: [object Object]3 类的 prototype 属性和__proto__属性 大多数浏览器的 ES5 实现之中每一个对象都有__proto__属性指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖同时有prototype属性和__proto__属性因此同时存在两条继承链。 1子类的__proto__属性表示构造函数的继承总是指向父类。 2子类prototype属性的__proto__属性表示方法的继承总是指向父类的prototype属性。 class A { }class B extends A { }B.__proto__ A // true B.prototype.__proto__ A.prototype // true上面代码中子类B的__proto__属性指向父类A子类B的prototype属性的__proto__属性指向父类A的prototype属性。 这样的结果是因为类的继承是按照下面的模式实现的。 class A { }class B { }// B 的实例继承 A 的实例 Object.setPrototypeOf(B.prototype, A.prototype);// B 继承 A 的静态属性 Object.setPrototypeOf(B, A);const b new B();Object.setPrototypeOf方法的实现。 Object.setPrototypeOf function (obj, proto) {obj.__proto__ proto;return obj; }因此就得到了上面的结果。 Object.setPrototypeOf(B.prototype, A.prototype); // 等同于 B.prototype.__proto__ A.prototype;Object.setPrototypeOf(B, A); // 等同于 B.__proto__ A;这两条继承链可以这样理解作为一个对象子类B的原型__proto__属性是父类A作为一个构造函数子类B的原型对象prototype属性是父类的原型对象prototype属性的实例。 B.prototype Object.create(A.prototype); // 等同于 B.prototype.__proto__ A.prototype;extends关键字后面可以跟多种类型的值。 class B extends A { }上面代码的A只要是一个有prototype属性的函数就能被B继承。由于函数都有prototype属性除了Function.prototype函数因此A可以是任意函数。 下面讨论两种情况。第一种子类继承Object类。 class A extends Object { }A.__proto__ Object // true A.prototype.__proto__ Object.prototype // true这种情况下A其实就是构造函数Object的复制A的实例就是Object的实例。 第二种情况不存在任何继承。 class A { }A.__proto__ Function.prototype // true A.prototype.__proto__ Object.prototype // true这种情况下A作为一个基类即不存在任何继承就是一个普通函数所以直接继承Function.prototype。但是A调用后返回一个空对象即Object实例所以A.prototype.__proto__指向构造函数Object的prototype属性。 3.1 实例的 proto 属性 子类实例的__proto__属性的__proto__属性指向父类实例的__proto__属性。也就是说子类的原型的原型是父类的原型。 var p1 new Point(2, 3); var p2 new ColorPoint(2, 3, red);p2.__proto__ p1.__proto__ // false p2.__proto__.__proto__ p1.__proto__ // true上面代码中ColorPoint继承了Point导致前者原型的原型是后者的原型。 因此通过子类实例的__proto__.__proto__属性可以修改父类实例的行为。 p2.__proto__.__proto__.printName function () {console.log(Ha); };p1.printName() // Ha上面代码在ColorPoint的实例p2上向Point类添加方法结果影响到了Point的实例p1。 4 原生构造函数的继承 原生构造函数是指语言内置的构造函数通常用来生成数据结构。ECMAScript 的原生构造函数大致有下面这些。 Boolean()Number()String()Array()Date()Function()RegExp()Error()Object() 以前这些原生构造函数是无法继承的比如不能自己定义一个Array的子类。 function MyArray() {Array.apply(this, arguments); }MyArray.prototype Object.create(Array.prototype, {constructor: {value: MyArray,writable: true,configurable: true,enumerable: true} });上面代码定义了一个继承 Array 的MyArray类。但是这个类的行为与Array完全不一致。 var colors new MyArray(); colors[0] red; colors.length // 0colors.length 0; colors[0] // red之所以会发生这种情况是因为子类无法获得原生构造函数的内部属性通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this也就是说原生构造函数的this无法绑定导致拿不到内部属性。 ES5 是先新建子类的实例对象this再将父类的属性添加到子类上由于父类的内部属性无法获取导致无法继承原生的构造函数。比如Array构造函数有一个内部属性[[DefineOwnProperty]]用来定义新属性时更新length属性这个内部属性无法在子类获取导致子类的length属性行为不正常。 下面的例子中我们想让一个普通对象继承Error对象。 var e {};Object.getOwnPropertyNames(Error.call(e)) // [ stack ]Object.getOwnPropertyNames(e) // []上面代码中我们想通过Error.call(e)这种写法让普通对象e具有Error对象的实例属性。但是Error.call()完全忽略传入的第一个参数而是返回一个新对象e本身没有任何变化。这证明了Error.call(e)这种写法无法继承原生构造函数。 ES6 允许继承原生构造函数定义子类因为 ES6 是先新建父类的实例对象this然后再用子类的构造函数修饰this使得父类的所有行为都可以继承。下面是一个继承Array的例子。 class MyArray extends Array {constructor(...args) {super(...args);} }var arr new MyArray(); arr[0] 12; arr.length // 1arr.length 0; arr[0] // undefined上面代码定义了一个MyArray类继承了Array构造函数因此就可以从MyArray生成数组的实例。这意味着ES6 可以自定义原生数据结构比如Array、String等的子类这是 ES5 无法做到的。 上面这个例子也说明extends关键字不仅可以用来继承类还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上定义自己的数据结构。下面就是定义了一个带版本功能的数组。 class VersionedArray extends Array {constructor() {super();this.history [[]];}commit() {this.history.push(this.slice());}revert() {this.splice(0, this.length, ...this.history[this.history.length - 1]);} }var x new VersionedArray();x.push(1); x.push(2); x // [1, 2] x.history // [[]]x.commit(); x.history // [[], [1, 2]]x.push(3); x // [1, 2, 3] x.history // [[], [1, 2]]x.revert(); x // [1, 2]上面代码中VersionedArray会通过commit方法将自己的当前状态生成一个版本快照存入history属性。revert方法用来将数组重置为最新一次保存的版本。除此之外VersionedArray依然是一个普通数组所有原生的数组方法都可以在它上面调用。 下面是一个自定义Error子类的例子可以用来定制报错时的行为。 class ExtendableError extends Error {constructor(message) {super();this.message message;this.stack (new Error()).stack;this.name this.constructor.name;} }class MyError extends ExtendableError {constructor(m) {super(m);} }var myerror new MyError(ll); myerror.message // ll myerror instanceof Error // true myerror.name // MyError myerror.stack // Error // at MyError.ExtendableError // ...注意继承Object的子类有一个行为差异。 class NewObj extends Object{constructor(){super(...arguments);} } var o new NewObj({attr: true}); o.attr true // false上面代码中NewObj继承了Object但是无法通过super方法向父类Object传参。这是因为 ES6 改变了Object构造函数的行为一旦发现Object方法不是通过new Object()这种形式调用ES6 规定Object构造函数会忽略参数。 5 Mixin 模式的实现 Mixin 指的是多个对象合成一个新的对象新对象具有各个组成成员的接口。它的最简单实现如下。 const a {a: a }; const b {b: b }; const c {...a, ...b}; // {a: a, b: b}上面代码中c对象是a对象和b对象的合成具有两者的接口。 下面是一个更完备的实现将多个类的接口“混入”mix in另一个类。 function mix(...mixins) {class Mix {constructor() {for (let mixin of mixins) {copyProperties(this, new mixin()); // 拷贝实例属性}}}for (let mixin of mixins) {copyProperties(Mix, mixin); // 拷贝静态属性copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性}return Mix; }function copyProperties(target, source) {for (let key of Reflect.ownKeys(source)) {if ( key ! constructor key ! prototype key ! name) {let desc Object.getOwnPropertyDescriptor(source, key);Object.defineProperty(target, key, desc);}} }上面代码的mix函数可以将多个对象合成为一个类。使用的时候只要继承这个类即可。 class DistributedEdit extends mix(Loggable, Serializable) {// ... }JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript
http://www.zqtcl.cn/news/484128/

相关文章:

  • 湖北网站建设路建设工程安全事故在哪个网站查
  • 建筑公司查询网站网站开发 系统需求文档
  • 温州做网站的公司有哪些宝塔搭建wordpress主机地址
  • 重庆商务网站建设南昌新力中心 nanchang sinic center
  • 潍坊建设厅官方网站店铺网络营销策划方案
  • 东营聊城网站建设博客论坛用wordpress
  • 哈尔滨中国建设银行网站首页seo快速入门教程
  • 网站建设项目环境影响评价目录南宁网站建设索王道下拉
  • 广州富邦物流网站建设南宁住房和城乡建设部网站
  • asp.net 公司网站全面的移动网站建设
  • 中国空间站官网app下载平台有哪些
  • 做外贸网站报价单做网站需要什么证件吗
  • 网站可以做视频链接东红物流网站建设规划书
  • 自己的网站网站免费部署
  • 广州专业的网站建设公司镇海seo关键词优化费用
  • 网站建设英文字体格式网络技术培训内容
  • 郑州公司网站设计在西宁做网站可以吗
  • 做最好的言情网站南通优普营销网站建设
  • 毕业设计网站可以做什么辽宁省建设厅网站更新
  • 同城信息网站建设牡丹江网站推广
  • 四川省城乡住房建设部网站首页商丘网站制作公司一二三网络推广
  • asp网站开发全程视频免费发布招聘信息平台
  • 机械网站建设开发网站如何做收录排行
  • 成都市学校网站建设怎样做网站二维码
  • 网站建设企业电话在线照片处理编辑器
  • 长沙建设网站企业wordpress 亲子模板下载
  • 济南seo整站优化价格织梦网站做404页面
  • 石家庄做标书的网站如何选取网站关键词
  • 摄影作品网站推荐wordpress用插件注册
  • pc端兼手机端网站模板中国网站 服务器