js学习手册和es6新增知识10关于class类_第1页
js学习手册和es6新增知识10关于class类_第2页
js学习手册和es6新增知识10关于class类_第3页
js学习手册和es6新增知识10关于class类_第4页
js学习手册和es6新增知识10关于class类_第5页
已阅读5页,还剩22页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Class 基本语法 概述JavaScript 语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子。function Po(x,y)上面这种写法跟传统的面象语言(比如 C+和Java)差异很大,很容易让新学习这门语言的程序员感到困惑。ES6 提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面象编程的语法而已。上面的代码用ES6 的“类”改写,就是下面这样。/定义类 cla

2、ss Poconstructor(x, y) this.x = x; this.y = y;toString() return ( + this.x + , + this.y + );this.x = x; this.y = y;Ptotype.toString = function () return ( + this.x + , + this.y + );上面代码定义了一个“类”,可以看到里面有一个 constructor 方法,这就是构造方法,而 this 关键字则代表实例对象。也就是说,ES5 的构造函数 Po,对应 ES6 的 Po类的构造方法。Po类除了构造方法,还定义了

3、一个 toString 方法。注意,定义“类”的方法的时候,前面不需要加上 function 这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。ES6 的类,完全可以看作构造函数的另一种写法。class Potypeof Po / functionPo = Ptotype.constructor / true上面代码表明,类的数据类型就是函数,类本身就指向构造函数。构造函数的 prototype 属性,在ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的 prototype 属性上面。在类的实例上面调用方法,其实就是调用原型上的方法。c

4、lass B let b = new B();b.constructor = B.prototype.constructor / trueclass Po constructor()/ . toString()/ . toValue()/ . / 等同于Ptotype = toString(),toValue();/ .上面代码中,b 是B 类的实例,它的 constructor 方法就是B 类原型的 constructor方法。由于类的方法都定义在 prototype 对象上面,所以类的新方法可以添加在 prototype对象上面。方法可以很方便地一次向类添加多个方法。class

5、Poconstructor()/ .Objesign(Ptotype, toString(),toValue();prototype 对象的constructor 属性,直接指向“类”的本身,这与 ES5 的行为是一致的。Ptotype.constructor = Po / true所有定义的方法,都是不可枚举的(non-enumerable)。另外,类的class Poconstructor(x, y) / .toString() / .Object.keys(Ptotype)/ Object.getOwnPropertyNames(Ptotype)/

6、 constructor,toString上面代码中,toString 方法是ES5 的行为不一致。类定义的方法,它是不可枚举的。这一点与var Po= function (x, y)/ .;PoObjesign/ toStringObject.getOwnPropertyNames(Ptotype)上面代码采用ES5 的写法,方法就是可枚举的。类的属性名,可以采用表达式。上面代码中,Square 类的方法名getArea,是从表达式得到的。constructor 方法constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法。一个类必须有 cons

7、tructor 方法,如果没有显式定义,一个空的 constructor 方被默认添加。constructor() constructor 方法默认返回实例对象(即 this),完全可以指定返回另外一个对象。class Foo constructor() return Object.create(null);let methodName = getArea; class Squareconstructor(length) / . methodName() / . toString/ constructor,toStringPtotype.toString = function() /

8、 .;Object.keys(Ptotype)上面代码中,constructor 函数返回一个全新的对象,结果导致实例对象不是 Foo 类的实例。类的实例对象生成类的实例对象的写法,与ES5 完全一样,也是使用 new 命令。如果忘记加上 new,像函数那样调用 Class,将会报错。/ 报错 var po = Po(2, 3);/ 正确 var po = new Po(2, 3);与ES5 一样,实例的属性除非显式定义在其本身(即定义在 this 对象上),否则都是定义在原型上(即定义在 class 上)。/定义类 class Po constructor(x,y) this.x

9、=this.y =x;y;toString()return ( +this.x + , + this.y + );var po = new Po(2, 3);po.toString() / (2, 3)po.hasOwnProperty(x) / truepo.hasOwnProperty(y) /truepofalsepo.hasOwnProperty(toString) /._proto .hasOwnProperty(toString) / truenew Foo() instanceof Foo/ false上面代码中, 和都是实例对象自身的属性(因为定义在变量上),所以hasOwnP

10、roperty 方法返回 true,而toString 是原型对象的属性(因为定义在。这些都与ES5 的行为保持一类上),所以hasOwnProperty 方法返回致。与ES5 一样,类的所有实例共个原型对象。var p1 = new Po(2,3);var p2 = new Po(3,2);p1. proto = p2. proto_/true都是Po的实例,它们的原型都是Po上面代码中,性是相等的。和,所以属这也意味着,可以通过实例的 proto 属性为Class 添加方法。上面代码在 p1 的原型上添加了一个 prName 方法,由于的原型就是的原型,因此 p2 也可以调用这个方法。而且

11、,此后新建的实例 p3 也可以调用这个方法。这意味着,使用实例的属性改写原型,必须相当谨慎,不使用,因为这会改变Class 的原始定义,影响到所有实例。name 属性由于本质上,ES6 的Class 只是ES5 的构造函数的一层包装,所以函数的许多特性都被Class 继承,包括 name 属性。class PoP / Po proto p2p1var p1 = new Po(2,3);var p2 = new Po(3,2);p1. proto .prName = function () return Oops ;p1.prName() / Oopsp2.prName() / Oo

12、psvar p3 = new Po(4,2);p3.prName() / Oops proto p2p1falsePothispoyx属性总是返回紧跟在关键字后面的类名。Class 表达式与函数一样,Class 也可以使用表达式的形式定义。 getClaame() 上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是 MyClass 而不是Me,Me 只在Class 的代码可用,指代当前类。inst.getClaame() / MeM / ReferenceError: Me is not defined上面代码表示,Me 只在Class有定义。如果Class没用到的话,可

13、以省略 Me,也就是可以写成下面的形式。const MyClass = class /* . */ ;采用Class 表达式,可以写出立即执行的Class。let= new class constructor(name) = name;sayName() console.log();();.sayName(); / let inst = new MyClass();return M;const MyClass = class Me classname是一个立即执行的Class 的实例。上面代码中,不存在变量Class 不存在变量(hoist),

14、这一点与ES5 完全不同。类使用,定义在后,这样会报错,因为ES6 不会把变量上面代码中,提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。上面的代码不会报错,因为继承的时候,已经有定义了。但是,如果存在Class 的,上面代码就会报错,因为 class 会被到代码头部,而 let 命令是不的,所以导致 class 继承 Foo 的时候,Foo 还没有定义。严格模式类和模块的,默认就是严格模式,所以不需要使用 use strict 指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把

15、整个语言升级到了严格模式。Class 的继承FooFooclasslet Foo = class ; class Bar extends Foo Foonew Foo(); / ReferenceErrorclass Foo 基本用法Class 之间可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。class ColorPo extends Po 上面代码定义了一个 ColorPo类,该类通过 extends 关键字,继承了 Po类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于了一个 Po类。下面,在 ColorPo加上

16、代码。class ColorPoextends Poconstructor(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 方法,否则新建实例时会报错。这

17、是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。class Po /* . */ class ColorPoextends Poconstructor() let cp = new ColorPo(); / ReferenceError上面代码中,ColorPo继承了父类 Po,但是它的构造函数没有调用 super 方法,导致新建实例错。ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this上面(Parent.apply(this))。ES6 的继承机制完全不同

18、,实质是先创造父类的实例对象 this(所以必须先调用 super 方法),然后再用子类的构造函数修改 this。如果子类没有定义 constructor 方法,这个方被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor 方法。另一个需要注意的地方是,在子类的构造函数中,只有调用 super 之后,才可以使用this 关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super 方法才能返回父类实例。class Po 上面代码中,子类的 constructor 方法没有调用 super 之前,就使用 this 关键字,结果报错,而放在

19、super 方法之后就是正确的。下面是生成子类实例的代码。和 Po两个类的实例,这与ES5 的上面代码中,实例对象 cp 同时是 ColorPo行为完全一致。let cp = new ColorPo(25, 8, green);cp instanceof ColorPo/ truecp instanceof Po/ trueconstructor(x, y) this.x = x; this.y = y;class ColorPoextends Poconstructor(x, y, color) this.color = color; / ReferenceErrorsuper(x, y);

20、 this.color = color; / 正确 constructor(.args) super(.args);类的 prototype 属性和proto属性大多数浏览器的ES5 实现之中,每一个对象都有 proto_属性,指应的构造函数的prototype 属性。Class 作为构造函数的语法糖,同时有prototype 属性和 proto 属性,因此同时存在两条继承链。(1)子类的 proto 属性,表示构造函数的继承,总是指向父类。(2)子类 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的prototype 属性。上面代码中,子类B 的 proto 属性

21、指向父类A,子类B 的prototype 属性的 proto 属性指向父类A 的prototype 属性。这样的结果是因为,类的继承是按照下面的模式实现的。对象的扩展一章给出过 Object.setPrototypeOf 方法的实现。因此,就得到了上面的结果。Object.setPrototypeOf(B.prototype, A.prototype);Object.setPrototypeOf = function (obj, proto) obj._proto = proto;return obj;class A class B / B 的实例继承A 的实例 Object.setProto

22、typeOf(B.prototype, A.prototype);/ B 继承A 的静态属性 Object.setPrototypeOf(B, A);class A class B extends A B. proto = A / trueB.prototype. proto = A.prototype / true这两条继承链,可以这样理解:作为一个对象,子类(B)的原型( proto 属性)是父类(A);作为一个构造函数,子类(B)的原型(prototype 属性)是父类的实例。Extends 的继承目标extends 关键字后面可以跟多种类型的值。上面代码的A,只要是一个有 protot

23、ype 属性的函数,就能被B 继承。由于函数都有prototype 属性,因此A 可以是任意函数。下面,三种特殊情况。第一种特殊情况,子类继承Object 类。这种情况下,A 其实就是构造函数 Object 的,A 的实例就是 Object 的实例。第二种特殊情况,不存在任何继承。class A class A extends Object A. proto = Object / trueA.prototype. proto = Ototype / trueclass B extends A B.prototype = new A();/ 等同于B.prototype. pr

24、oto_ = A.prototype;/ 等同于B.prototype. proto_ = A.prototype;Object.setPrototypeOf(B, A);/ 等同于B. proto_ = A;这种情况下,A 作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Ftotype。但是,A 调用后返回一个空对象(即 Object 实例),所以A.prototype. proto 指向构造函数(Object)的prototype 属性。第三种特殊情况,子类继承 null。这种情况与第二种情况非常像。A 也是一个普通函数,所以直接继承Funciton.

25、prototype。但是,A 调用后返回的对象不继承任何方法,所以它的 proto 指向Ftotype,即实质上执行了下面的代码。Object.getPrototypeOf()Object.getPrototypeOf 方法可以用来从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。super 关键字super 这个关键字,有两种用法,含义不同。(1)作为函数调用时(即 super(.args)),super 代表父类的构造函数。Object.getPrototypeOf(ColorPo) = Po/ trueclass C extends null

26、constructor() return Object.create(null); class A extends null A. proto = Ftotype / trueA.prototype. proto = undefined / trueA. proto = Ftotype / trueA.prototype. proto = Ototype / true(2)作为对象调用时(即 suprop 或super.method()),super 代表父类。注意,此时 super 即可以父类实例的属性和方法,也可以父类的静态方法

27、。上面代码中,子类通过 super 关键字,调用父类实例的_p 属性。由于,对象总是继承其他对象的,所以可以在任意一个对象中,使用 super 关键字。实例的proto属性子类实例的proto属性的proto属性,指向父类实例的proto属性。也就是说,子类的原型的原型,是父类的原型。上面代码中,ColorPo继承了 Po,导致前者原型的原型是后者的原型。因此,通过子类实例的 proto ._proto 属性,可以修改父类实例的行为。var p1 = new Po(2, 3);var p2 = new ColorPo(2, 3, red);p2. proto = p1. proto_ / fa

28、lsep2. proto ._proto = p1. proto / truevar obj = toString() return MyObject: + super.toString();obj.toString(); / MyObject: object Objectclass B extends A get m() return this._p * super._p;set m() throw new Error(该属性只读);p2. proto . proto .prconsole.log(Ha);Name = function () p1.prName() / Ha上面代码在 Co

29、lorPo的实例 p2 上向Po类添加方法,结果影响到了 Po的实例 p1。原生构造函数的继承原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript 的原生构造函数大致有下面这些。()Number()String()Array()Date()Function()RegExp()Error()Object()以前,这些原生构造函数是无法继承的,比如,不能自己定义一个 Array 的子类。function MyArray() Array.apply(this, arguments);MyAtotype = Object.create(Atot

30、ype, constructor: value: MyArray, writable: true, configurable: true, enumerable: true);上面代码定义了一个继承Array 的 MyArray 类。但是,这个类的行为与 Array 完全不一致。之所以会发生这种情况,是因为子类无法获得原生构造函数的属性,通过。ES5 是先新建子类的实例对象 this,再属性无法获取,导致无法继承原生的构造Array.apply()或者分配给原型对象都将父类的属性添加到子类上,由于父类的函数。比如,Array 构造函数有一个属性DefineOwnProperty,用来定义新属性

31、时,更新 length 属性,这个行为不正常。属性无法在子类获取,导致子类的 length 属性ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象 this,然后再用子类的构造函数修饰 this,使得父类的所有行为都可以继承。下面是一个继承Array 的例子。上面代码定义了一个 MyArray 类,继承了 Array 构造函数,因此就可以从 MyArray 生成数组的实例。这意味着,ES6 可以自定义原生数据结构(比如Array、String 等)的子类,这是ES5 无法做到的。上面这个例子也说明,extends 关键字不仅可以用来继承类,还可以用来继承原生的构造函数。

32、因此可以在原生数据结构的基础上,定义自己的数据结构。下面就是定义了一个带版本功能的数组。class MyArray extends Array constructor(.args) super(.args);var arr = new MyArray(); arr0 = 12;arr.length / 1arr.length = 0; arr0 / undefinedvar colors = new MyArray(); colors0 = red; colors.length / 0colors.length = 0;colors0 / red上面代码中,VeredArray 结构会通过co

33、mmit 方法,将自己的当前状态存入history 属性,然后通过 revert 方法,可以撤销当前版本,回到上一个版本。除此之外,VeredArray 依然是一个数组,所有原生的数组方法都可以在它上面调用。下面是一个自定义 Error 子类的例子。class ExtendableError extends Error constructor(message) super();this.message = message; this.stack = (new Error().stack; = ;class VeredArray ex

34、tends Array constructor() super(); this.history = ;commit() this.history.push(this.slice();revert() this.splice(0, this.length, .this.historythis.history.length -1);var x = new VeredArray();x.push(1);x.push(2);x / 1, 2x.history / mit();x.history / , 1, 2x.push(3); x / 1, 2, 3x.revert();x / 1, 2注意,继承

35、 Object 的子类,有一个行为差异。上面代码中,NewObj 继承了 Object,但是无法通过 super 方法向父类 Object 传参。这是因为ES6 改变了Object 构造函数的行为,一旦发现 Object 方法不是通过 newObject()这种形式调用,ES6 规定 Object 构造函数会忽略参数。Class 的取值函数(getter)和存值函数(setter)与ES5 一样,在Class可以使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,该属性的存取行为。class MyClass constructor() / . get prop() return

36、 getter;claewObj extends Object constructor()super(.arguments);var o = new NewObj(attr: true); console.log(o.attr = true); / falseclass MyError extends ExtendableError constructor(m) super(m);var myerror = new MyError(ll);myerror.message / llmyerror instanceof Error / / MyErrormyerr

37、or.stack/ Error/at MyError.ExtendableError/.上面代码中,prop 属性有对应的存值函数和取值函数,因此赋值和义了。行为都被自定存值函数和取值函数是设置在属性的descriptor 对象上的。上面代码中,存值函数和取值函数是定义在 html 属性的描述对象上面,这与 ES5 完全一致。class CustomHTMLElement constructor(element) this.element = element;get html() return this.element.innerHTML;set html(value) this.elemen

38、t.innerHTML = value;var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLEtotype, html);get in descriptor / trueset in descriptor / trueset prop(value) console.log(setter: +value);let inst = new MyClass();p = 123;/ setter: 123 p/ getterClass 的 Generator 方法如果某个方法之前加上星号

39、(*),就表示该方法是一个Generator 函数。class Foo constructor(.args) this.args = args;* Symbol.iterator() for (let arg of this.args) yield arg;for (let x of new Foo(console.log(x);o, world) /o/ world上面代码中,Foo 类的Symbol.iterator 方法前有一个星号,表示该方法是一个Generator 函数。Symbol.iterator 方法返回一个Foo 类的默认遍历器,for.of 循环会自动调用这个遍历器。Cla

40、ss 的静态方法类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。class Foo sic classMethod() return o;Foo.classMethod() / ovar foo = new Foo();sic上面代码中,类的方法前有关键字,表明该方法是一个静态方法,可以直接在 Foo 类上调用(Foo.classMethod()),而不是在 Foo 类的实例上调用。如果在实例上调用静态方抛出一个错误,表示不存在该方法。父类的静态方法,可以被子类继承。class Fo

41、o sic classMethod() return o;class Bar extends Foo Bar.classMethod(); / o上面代码中,父类 Foo 有一个静态方法,子类Bar 可以调用这个方法。静态方法也是可以从 super 对象上调用的。class Foo sic classMethod() return o;class Bar extends Foo sic classMethod() return super.classMethod() + ,too;Bar.classMethod();Class 的静态属性和实例属性sicclassMethodFoofoo.cl

42、assMethod()/ TypeError: undefined is not a function静态属性指的是Class 本身的属性,即 Cpname,而不是定义在实例对象(this)上的属性。上面的写法为 Foo 类定义了一个静态属性 prop。目前,只有这种写法可行,因为ES6 明确规定,Class属性。只有静态方法,没有静态 / 写法二 sic prop: 2ES7 有一个静态属性的提案,目前Babel 转支持。这个提案对实例属性和静态属性,都规定了新的写法。(1)类的实例属性类的实例属性可以用等式,写入类的定义之中。上面代码中,myProp 就是 MyClass

43、的实例属性。在 MyClass 的实例上,可以个属性。这以前,定义实例属性,只能写在类的constructor 方法里面。class MyClass myProp = 42;constructor() console.log(this.myProp); / 42 Fp / undefined/ 以下两种写法都无效 class Foo / 写法一 prop: 2class Foo Fp = 1;Fp / 1this.se = 上面代码中,构造方法里面,定义了this.se 属性。有了新的写法以后,可以不在 constructor 方法里面定义。 se = 这种

44、写法比以前更清晰。为了可读性的目的,对于那些在 constructor 里面已经定义的实例属性,新写法允许直接列出。(2)类的静态属性类的静态属性只要在上面的实例属性写法前面,加上 sic 关键字就可以了。 sic mySicProp = 42;class MyClass class ReactCounter extendsponent constructor(props) suprops);this.se = count: 0;se;count: 0;class ReactCounter extendsponent constructorcount: 0;class ReactCounter

45、 extendsponent constructor(props) suprops);同样的,这个新写法大大方便了静态属性的表达。上面代码中,老写法的静态属性定义在类的外部。整个类生成以后,再生成静态属性。这样让人很容易忽略这个静态属性,也不符合相关代码应该放在一起的代码组织原则。(declarative),而不是赋值处理,语义更好。另外,新写法是显式new.属性令。ES6 为是从构造函数生成实例命令引入了一个属性,(在构造函数中)返回 new 命令作用于的那个构造函数。如果构造函数不是通过 new命令调用的,会返回,因此这个属性可以用来确定构造函数是怎么调用的。function (name) if (new.!= undefined) e

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论