甘肃省建设厅执业资格注册网站,沈阳网站建设培训班,上海个人医疗网站备案,产品众筹网站开发目录 1. 简介
2. 严格模式
3. constructor 方法
4. 类的实例对象
5. Class 表达式
6. 不存在变量提升
7. 私有方法
8. 私有属性
9. this 的指向
10. name 属性
11. Class 的取值函数#xff08;getter#xff09;和存值函数#xff08;setter#xff09;
12. Cl…目录 1. 简介
2. 严格模式
3. constructor 方法
4. 类的实例对象
5. Class 表达式
6. 不存在变量提升
7. 私有方法
8. 私有属性
9. this 的指向
10. name 属性
11. Class 的取值函数getter和存值函数setter
12. Class 的 Generator 方法
13. Class 的静态方法
14. Class 的静态属性和实例属性
14.1类的实例属性
14.2类的静态属性
15. new.target 属性 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 方法就是可枚举的。 类的属性名可以采用表达式。
let methodName getArea;
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
上面代码中 Square 类的方法名 getArea 是从表达式得到的。
2. 严格模式
类和模块的内部默认就是严格模式所以不需要使用 use strict 指定运行模式。只要你的代码写在类或模块之中就只有严格模式可用。 考虑到未来所有的代码其实都是运行在模块之中所以 ES6 实际上把整个语言升级到了严格模式。
3. constructor 方法
constructor 方法是类的默认方法通过 new 命令生成对象实例时自动调用该方法。一个类必须有 constructor 方法如果没有显式定义一个空的 constructor 方法会被默认添加。
class Point {
}
// 等同于
class Point {
constructor() {}
}
上面代码中定义了一个空的类 Point JavaScript 引擎会自动为它添加一个空的 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 new
4. 类的实例对象
生成类的实例对象的写法与 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() // Oops
var p3 new Point(4,2);
p3.printName() // Oops
上面代码在 p1 的原型上添加了一个 printName 方法由于 p1 的原型就是 p2 的原型因此 p2 也可以调用这个方法。而且此后新建的实例 p3 也可以调用 这个方法。这意味着使用实例的 __proto__ 属性改写原型必须相当谨慎不推荐使用因为这会改变“类”的原始定义影响到所有实例。
5. Class 表达式
与函数一样类也可以使用表达式的形式定义。
const MyClass class Me {
getClassName() {
return Me.name;
}
};
上面代码使用表达式定义了一个类。需要注意的是这个类的名字是 MyClass 而不是 Me Me 只在 Class 的内部代码可用指代当前类。
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 是一个立即执行的类的实例。
6. 不存在变量提升
类不存在变量提升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 还没有定义。
7. 私有方法
私有方法是常见需求但 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 值导致第三方无法获取到它们因此达到了私有方法和私有属性的效果
8. 私有属性
与私有方法一样ES6 不支持私有属性。目前有一个提案为 class 加了私有属性。方法是在属性名之前使用 # 表示。
class Point {
#x;
constructor(x 0) {
#x x; // 写成 this.#x 亦可
}
get x() { return #x }
set x(value) { #x value }
}
上面代码中 #x 就表示私有属性 x 在 Point 类之外是读取不到这个属性的。还可以看到私有属性与实例的属性是可以同名的比如 #x 与 get x() 。 私有属性可以指定初始值在构造函数执行时进行初始化
class Point {
#x 0;
constructor() {
#x; // 0
}
}
之所以要引入一个新的前缀 # 表示私有属性而没有采用 private 关键字是因为 JavaScript 是一门动态语言使用独立的符号似乎是唯一的可靠方法 能够准确地区分一种属性是否为私有属性。另外Ruby 语言使用 表示私有属性ES6 没有用这个符号而使用 # 是因为 已经被留给了 Decorator。 该提案只规定了私有属性的写法。但是很自然地它也可以用来写私有方法
class Foo {
#a;
#b;
#sum() { return #a #b; }
printSum() { console.log(#sum()); }
constructor(a, b) { #a a; #b b; }
}
9. this 的指向
类的方法内部如果含有 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 会指向该方法运行时所在的环境 因为找不到 print 方法而导致报错。 一个比较简单的解决方法是在构造方法中绑定 this 这样就不会找不到 print 方法了。
class Logger {
constructor() {
this.printName this.printName.bind(this);
}
// ...
}
另一种解决方法是使用箭头函数
class Logger {
constructor() {
this.printName (name there) {
this.print(Hello ${name});
};
}
// ...
}
还有一种解决方法是使用 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());
10. name 属性
由于本质上ES6 的类只是 ES5 的构造函数的一层包装所以函数的许多特性都被 Class 继承包括 name 属性。
class Point {}
Point.name // Point
name 属性总是返回紧跟在 class 关键字后面的类名。
11. Class 的取值函数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: 123
inst.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 完全一致。
12. Class 的 Generator 方法
如果某个方法之前加上星号 * 就表示该方法是一个 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 循环会自动调用这个遍历器。
13. Class 的静态方法
类相当于实例的原型所有在类中定义的方法都会被实例继承。如果在一个方法前加上 static 关键字就表示该方法不会被实例继承而是直接通过 类来调用这就称为“静态方法”。
class Foo {
static classMethod() {
return hello;
}
}
Foo.classMethod() // hello
var 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, too
14. Class 的静态属性和实例属性
静态属性指的是 Class 本身的属性即 Class.propName 而不是定义在实例对象 this 上的属性。
class Foo {
}
Foo.prop 1;
Foo.prop // 1
上面的写法为 Foo 类定义了一个静态属性 prop 。 目前只有这种写法可行因为 ES6 明确规定Class 内部只有静态方法没有静态属性
// 以下两种写法都无效
class Foo {
// 写法一
prop: 2
// 写法二
static prop: 2
}
Foo.prop // undefined
目前有一个静态属性的提案对实例属性和静态属性都规定了新的写法。
14.1类的实例属性 类的实例属性可以用等式写入类的定义之中。
class MyClass {
myProp 42;
constructor() {
console.log(this.myProp); // 42
}
}
上面代码中 myProp 就是 MyClass 的实例属性。在 MyClass 的实例上可以读取这个属性。 以前我们定义实例属性只能写在类的 constructor 方法里面。
class ReactCounter extends React.Component {
constructor(props) {
super(props);
this.state {
count: 0
};
}
}
上面代码中构造方法 constructor 里面定义了 this.state 属性。 有了新的写法以后可以不在 constructor 方法里面定义。
class ReactCounter extends React.Component {
state {
count: 0
};
}
这种写法比以前更清晰。 为了可读性的目的对于那些在 constructor 里面已经定义的实例属性新写法允许直接列出
class ReactCounter extends React.Component {
state;
constructor(props) {
super(props);
this.state {
count: 0
};
}
}
14.2类的静态属性
类的静态属性只要在上面的实例属性写法前面加上 static 关键字就可以了
class MyClass {
static myStaticProp 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
同样的这个新写法大大方便了静态属性的表达。
// 老写法
class Foo {
// ...
}
Foo.prop 1;
// 新写法
class Foo {
static prop 1;
}
上面代码中老写法的静态属性定义在类的外部。整个类生成以后再生成静态属性。这样让人很容易忽略这个静态属性也不符合相关代码应该放在一 起的代码组织原则。另外新写法是显式声明declarative而不是赋值处理语义更好。
15. new.target 属性
new 是从构造函数生成实例对象的命令。ES6 为 new 命令引入了一个 new.target 属性该属性一般用在构造函数之中返回 new 命令作用于的那个构造 函数。如果构造函数不是通过 new 命令调用的 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) {
super(length, length);
}
}
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 会报错。
总结
本博客源于本人阅读相关书籍和视频总结创作不易谢谢点赞支持。学到就是赚到。我是歌谣励志成为一名优秀的技术革新人员。
欢迎私信交流一起学习一起成长。
推荐链接 其他文件目录参照
“睡服“面试官系列之各系列目录汇总建议学习收藏