【读书笔记】ES6 Class的基本语法
class
- constructor()
- 实例对象
- 表达式
- 提升
- 私有方法和私有属性
- this
- name属性
- 取值函数和存值函数
- generator方法
- 静态方法
- 静态属性和实例属性
- new.target属性
class point { constructor(x, y) { this.x = x; this.y = y; } tostring() { return '(' + this.x + ',' + this.y + ')'; } }
1.constructor是构造方法。
2.this关键字则代表实例对象。
3.定义“类”的方法的时候,前面不需要加上function关键字,直接把函数定义放进去了就可以了。
4.方法之间不需要逗号分隔。
class point { } typeof point // "function" point === point.prototype.constructor // true
5.类的数据类型就是函数,类本身就指向构造函数。
point.protype = { constructor() {}, tostring() {}, tovalue() {} };
6.事实上,类的所有方法都定义在类的prototype属性上面。
b.constructor === b.prototype.constructor // true
7.在类的实例上调用方法,其实就是调用原型上的方法。
object.assign(point.prototype, { tostring() {}, tovalue() {} });
8.object.assign方法可以很方便地一次向类添加多个方法。
point.prototype.constructor === point // true
9.prototype对象的constructor属性,直接指向“类”的本身。
object.keys(point.prototype); // [] object.getownpropertynames(point.prototype); // ["constructor", "tostring"]
10.类的内部所有定义的方法,都是不可枚举的。
let methodname = 'getarea'; class square { constructor(length) { } [methodname]() { } }
11.类的属性名,可以采用表达式。
类和模块的内部,默认就是严格模式,所以不需要使用 use strict 指定运行模式。
只要你的代码写在类或模块之中,就只有严格模式可用。
es6实际上把整个语言升级到了严格模式。
constructor()
1.constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
class point { } // 等同于 class point { constructor() {} }
2.一个类必须有constructor方法,如果没有显示定义,一个空的constructor方法会被默认添加。
class foo { constructor() { return object.create(null); } } new foo() instanceof foo // false
3.constructor方法默认返回实例对象,即this,完全可以指定返回另一个对象。
class foo { constructor() { return object.create(null); } } foo(); // typeerror
4.类必须使用new调用,否则会报错。
这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
实例对象
class point { ... } var point = point(2, 3); // 报错 var point = new point(2, 3); // 正确
1.生成类的实例对象的写法,与es5完全一样,也是使用new命令。
class point { constructor(x, y) { this.x = x; this.y = y; } tostring() { return '(' +this.x + ', ' + this.y + '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
2.与es5一样,实例的属性除非显式定义在其本身,即定义在this对象上,否则都是定义在原型上,即定义在class上。
hasownproperty用来检测是不是实例的属性。
var p1 = new point(2, 3); var p2 = new point(3, 2); p1._proto_ === p2._proto_ // ture
3.与es5一样,类的所有实例共享一个原型对象。
var p1 = new point(2, 3); var p2 = new point(3, 2); p1._proto_.printname = function() { return 'oops' }; p1.printname() // "oops" p2.printname() // "oops" var p3 = new point(4,2); p3.printname() // "oops"
4.可以通过实例的_proto_属性为“类”添加方法。
生产环境中,我们可以使用object.getprototypeof方法来获取实例对象的原型。
表达式
const myclass = class me { getclassname() { return me.name; } };
let inst = new myclass(); inst.getclassname(); // me me.name // referenceerror
const myclass = class {...};
1.与函数一样,类也可以使用表达式的形式定义。
这个类的名字是myclass而不是me,me只在class的内部代码可用,指代当前类。
如果类的内部没用到的话,可以省略me。
let person = new class { constructor(name) { this.name = name; } sayname() { console.log(this.name); } }('张三'); person.sayname(); // "张三"
2.采用class表达式,可以写出立即执行的class。
提升
new foo(); // referenceerror class foo{}
1.类不存在变量提升。
{ let foo = class {}; class bar extends foo {} }
2.必须保证子类在父类之后定义。
私有方法和私有属性
class widget { // 公有方法 foo(baz) { this._bar(baz); } // 私有方法 _bar(baz) { return this.snaf = baz; } }
1.私有方法是常见需求,但es6不提供,只能通过变通方法模拟实现。
一种做法是在命名上加以区别。
_bar方法前面的下划线,表示这是一个只限于内部使用的私有方法。
class widget { foo(baz) { bar.call(this, baz); } ... } function bar(baz) { return this.snaf = baz; }
2.另一种方法就是索性将私有方法移除模块,因为模块内部的所有方法都是对外可见的。
const bar = symbol('bar'); const snaf = symbol('snaf'); export default class myclass { // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } ... }
3.还有一种方法就是利用symbol值的唯一性,将私有方法的名字命名为一个symbol值。
this
class logger { printname(name = 'there') { this.print(`hello ${name}`); } print(text) { console.log(text); } } const logger = new logger(); const { printname } = logger; printname(); // typeerror
1.类的方法内部如果含有this,它默认指向类的实例。
但是,必须非常小心,一点单独使用该方法,很可能报错。
如果将这个方法提取出来单独使用,this会指向该方法运行时所作的环境,因为找不到print方法而导致报错。
class logger { constructor() { this.printname = this.printname.bind(this); } ... }
2.一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。
class logger { constructor() { this.printname = (name = 'there') => { this.print(`hellow ${name}`); }; } ... }
3.另一种解决方法是使用箭头函数。
function selfish(target) { const cache = new weakmap(); const handler = { get(target, key) { const value = refkect.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());
4.还有一种解决方法是使用proxy,获取方法的时候,自动绑定this。
name属性
class point {} point.name // "point"
1.由于本质上,es6的类只是es5的构造函数的一层包装,所以函数的许多特性都被class继承,包括name属性。
name属性总是返回紧跟在class关键字后面的类名。
取值函数和存值函数
class myclass { constructor() { ... } get prop() { return 'getter'; } set prop(value) { console.log('setter: ' + value); } } let inst = new myclass(); inst.prop = 123; // setter: 123 inst.prop // 'getter'
1.与es5一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。
class customhtmlelement { constructor(element) { this.element = element; } get html() { return this.element.innerhtml; } set html(value) { this.element.innerhtml = value; } } var decriptor = object.getownpropertydescroptor(customhtmlelement.prototype, "html"); "get" in decriptor // true "set" in decriptor // true
2.存值函数和取值函数是设置在属性的descriptor对象上的。
上面代码中,存值函数和取值函数是定义在html属性的描述对象上面。这与es5完全一致。
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
1.如果某个方法之前加上星号(*),就表示该方法是一个 generator 函数。
foo类的symbol.iterator方法前有一个星号,表示该方法是一个generator函数。
symbol.iterator方法返回一个foo类的默认遍历器,for...of 循环会自动调用这个遍历器。
静态方法
class foo { static classmethod() { return 'hello'; } } foo.classmethod(); // "hello" var foo = new foo(); foo.classmethod(); // typeerror
1.类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class foo { static bar() { this.baz(); } statoc baz() { console.log('hello'); } baz() { console.log('world'); } } foo.bar(); // hello
2.如果静态方法包含this关键字,这个this指的是类,而不是实例。
另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
class foo { static classmethod() { return 'hello'; } } class bar extends foo { } bar.classmethod(); // 'hello'
3.父类的静态方法,可以被子类继承。
class foo { static classmethod() { return 'hello'; } } class bar extends foo { static classmethod() { return super.classmethod() + ', too'; } } bar.classmehtod(); // "hello, too"
4.静态方法也是可以从super对象上调用的。
静态属性和实例属性
class foo { } foo.prop = 1; foo.prop // 1
// 以下两种写法都无效 class foo { // 写法一 prop: 2 // 写法二 static prop: 2 } foo.prop // undefined
1.静态属性指的是class本身的属性,即class.propname,而不是定义在实例对象(this)上的属性。
目前,只有这种写法可行,因为es6明确规定,class内部只有静态方法,没有静态属性。
class reactcounter extends react.component { constructor(props) { super(props); this.state = { count: 0 }; } }
2.以前,我们定义实例属性,只能写在类的constructor方法里面。
new.target属性
function person(name) { if (new.target !== undefined) { this.name = name; } else { throw new eooro('必须使用new命令生成实例'); } } // 另一种写法 function person(name) { if (new.target === person) { this.name = name; } else { throw new error(''); } } var person = new person('张三'); // 正确 var notaperson = person.call(person, '张三'); // 报错
1.new是从构造函数生成实例对象的命令。
es6为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。
如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
class rectangle { constructor(lenght, width) { console.log(new.target === rectangle); this.length = length; this.width = width; } } var obj = new rectangle(3, 4); // true
class rectangle { constructor(length, width) { console.log(new.target === rectangle); ... } } class square extends rectangle { constructor(length) { super(legnth, length); } } var obj = new square(3); // false
2.class内部调用new.target,返回当前class。
需要注意的是,子类继承父类时,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); // 正确
3.利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。
注意,在函数外部,使用new.target会报错。
上一篇: 忍俊不禁的几段尬笑
下一篇: Python学习之旅(十九)