深入浅出javaScript中的Class

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作 只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已.

<span>function</span> <span>Point</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>)</span> <span>{</span>  
    <span>this</span><span>.</span><span>x</span> <span>=</span> <span>x</span><span>;</span>  
    <span>this</span><span>.</span><span>y</span> <span>=</span> <span>y</span><span>;</span>
<span>}</span>
 
<span>Point</span><span>.</span><span>prototype</span><span>.</span><span>toString</span> <span>=</span> <span>function</span> <span>(</span><span>)</span> <span>{</span>  
    <span>return</span> <span>'('</span> <span>+</span> <span>this</span><span>.</span><span>x</span> <span>+</span> <span>', '</span> <span>+</span> <span>this</span><span>.</span><span>y</span> <span>+</span> <span>')'</span><span>;</span>
<span>}</span>
 
 
<span>//定义类 </span>
<span>class</span> <span>Point</span> <span>{</span>  
    <span>constructor</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>)</span> <span>{</span>    
        <span>this</span><span>.</span><span>x</span> <span>=</span> <span>x</span><span>;</span>    
        <span>this</span><span>.</span><span>y</span> <span>=</span> <span>y</span><span>;</span>  
    <span>}</span>
 
    <span>toString</span><span>(</span><span>)</span> <span>{</span>    
        <span>return</span> <span>'('</span> <span>+</span> <span>this</span><span>.</span><span>x</span> <span>+</span> <span>', '</span> <span>+</span> <span>this</span><span>.</span><span>y</span> <span>+</span> <span>')'</span><span>;</span>  
    <span>}</span>
<span>}</span>
 
<span>// 类实际上就是方法</span>
<span>typeof</span> <span>Point</span> <span>// function</span>
 
<span>// 构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。</span>
<span>class</span> <span>B</span> <span>{</span><span>}</span>
<span>let</span> <span>b</span> <span>=</span> <span>new</span> <span>B</span><span>(</span><span>)</span><span>;</span>
<span>b</span><span>.</span><span>constructor</span> <span>===</span> <span>B</span><span>.</span><span>prototype</span><span>.</span><span>constructor</span> <span>// true</span>
 
<span>// prototype对象的constructor属性,直接指向“类”的本身,这与ES5的行为是一致的。</span>
<span>b</span><span>.</span><span>prototype</span><span>.</span><span>constructor</span> <span>===</span> <span>B</span> <span>// true</span>

另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

<span>class</span> <span>Point</span> <span>{</span>  
    <span>constructor</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>)</span> <span>{</span>    
         <span>this</span><span>.</span><span>x</span> <span>=</span> <span>x</span><span>;</span>
         <span>this</span><span>.</span><span>y</span> <span>=</span> <span>y</span><span>;</span>
    <span>}</span>
 
    <span>toString</span><span>(</span><span>)</span> <span>{</span>    
        <span>// ...  </span>
    <span>}</span>
<span>}</span>
 
<span>Object</span><span>.</span><span>keys</span><span>(</span><span>Point</span><span>.</span><span>prototype</span><span>)</span> <span>// [] </span>
<span>Object</span><span>.</span><span>getOwnPropertyNames</span><span>(</span><span>Point</span><span>.</span><span>prototype</span><span>)</span> <span>// ["constructor","toString"]</span>
 
<span>// 与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。</span>
<span>var</span> <span>point</span> <span>=</span> <span>new</span> <span>Point</span><span>(</span><span>2</span><span>,</span> <span>3</span><span>)</span><span>;</span>
<span>point</span><span>.</span><span>toString</span><span>(</span><span>)</span> <span>// (2, 3)</span>
 
<span>point</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>'x'</span><span>)</span> <span>// true </span>
<span>point</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>'y'</span><span>)</span> <span>// true </span>
<span>point</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>'toString'</span><span>)</span> <span>// false </span>
<span>point</span><span>.</span><span>__proto__</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>'toString'</span><span>)</span> <span>// true</span>
 
 
<span>// toString方法是Point类内部定义的方法,它是不可枚举的。这一点与ES5的行为不一致。</span>
<span>var</span> <span>Point</span> <span>=</span> <span>function</span> <span>(</span><span>x</span><span>,</span> <span>y</span><span>)</span> <span>{</span> <span>}</span><span>;</span>
<span>Point</span><span>.</span><span>prototype</span><span>.</span><span>toString</span> <span>=</span> <span>function</span><span>(</span><span>)</span> <span>{</span> <span>}</span><span>;</span>
<span>Object</span><span>.</span><span>keys</span><span>(</span><span>Point</span><span>.</span><span>prototype</span><span>)</span> <span>// ["toString"] </span>
<span>Object</span><span>.</span><span>getOwnPropertyNames</span><span>(</span><span>Point</span><span>.</span><span>prototype</span><span>)</span> <span>// ["constructor","toString"]</span>
 
 
<span>point</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>'x'</span><span>)</span> <span>// true </span>
<span>point</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>'y'</span><span>)</span> <span>// true </span>
<span>point</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>'toString'</span><span>)</span> <span>// false </span>

constructor方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空 的constructor方法会被默认添加。 constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

<span>class</span> <span>Foo</span> <span>{</span>  <span>constructor</span><span>(</span><span>)</span> <span>{</span>    <span>return</span> <span>Object</span><span>.</span><span>create</span><span>(</span><span>null</span><span>)</span><span>;</span>  <span>}</span> <span>}</span>
<span>new</span> <span>Foo</span><span>(</span><span>)</span> <span>instanceof</span> <span>Foo</span> <span>// false</span>

上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。

类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

<span>class</span> <span>Foo</span> <span>{</span>  <span>constructor</span><span>(</span><span>)</span> <span>{</span>    <span>return</span> <span>Object</span><span>.</span><span>create</span><span>(</span><span>null</span><span>)</span><span>;</span>  <span>}</span> <span>}</span>
<span>Foo</span><span>(</span><span>)</span> <span>// TypeError: Class constructor Foo cannot be invoked without 'new'</span>

静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接 通过类来调用,这就称为“静态方法”。

<span>class</span> <span>Foo</span> <span>{</span>  <span>static</span> <span>classMethod</span><span>(</span><span>)</span> <span>{</span>    <span>return</span> <span>'hello'</span><span>;</span>  <span>}</span> <span>}</span>
<span>Foo</span><span>.</span><span>classMethod</span><span>(</span><span>)</span> <span>// 'hello'</span>
<span>var</span> <span>foo</span> <span>=</span> <span>new</span> <span>Foo</span><span>(</span><span>)</span><span>;</span>
<span>foo</span><span>.</span><span>classMethod</span><span>(</span><span>)</span> <span>// TypeError: foo.classMethod is not a function</span>
 
<span>// 父类的静态方法,可以被子类继承。</span>
<span>class</span> <span>Bar</span> <span>extends</span> <span>Foo</span> <span>{</span> <span>}</span>
<span>Bar</span><span>.</span><span>classMethod</span><span>(</span><span>)</span><span>;</span> <span>// 'hello'</span>
 
<span>// 静态方法也是可以从super对象上调用的。</span>
<span>class</span> <span>Bar</span> <span>extends</span> <span>Foo</span> <span>{</span>  
    <span>static</span> <span>classMethod</span><span>(</span><span>)</span> <span>{</span>    
        <span>return</span> <span>super</span><span>.</span><span>classMethod</span><span>(</span><span>)</span> <span>+</span> <span>', too'</span><span>;</span>  
    <span>}</span>
<span>}</span>
<span>Bar</span><span>.</span><span>classMethod</span><span>(</span><span>)</span><span>;</span>

静态方法的实现其实跟下面类似

<span>class</span> <span>Foo</span> <span>{</span>  
    <span>static</span> <span>classMethod</span><span>(</span><span>)</span> <span>{</span>    
        <span>return</span> <span>'hello'</span><span>;</span>  
    <span>}</span>
<span>}</span>
 
<span>// 等同于</span>
<span>function</span> <span>Foo</span><span>(</span><span>)</span><span>{</span><span>}</span>
<span>Foo</span><span>.</span><span>classMethod</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span>
    <span>return</span> <span>'hello'</span><span>;</span>
<span>}</span>

静态属性

静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。

<span>class</span> <span>Foo</span> <span>{</span> <span>}</span>
<span>Foo</span><span>.</span><span>prop</span> <span>=</span> <span>1</span><span>;</span>
<span>Foo</span><span>.</span><span>prop</span> <span>// 1</span>
 
<span>// 语法等同于</span>
<span>function</span> <span>Foo</span><span>(</span><span>)</span><span>{</span><span>}</span>
<span>Foo</span><span>.</span><span>prop</span> <span>=</span> <span>1</span>

目前,只有这种写法可行,因为ES6明确规定,Class内部只有静态方法,没有静态属性。

<span>// 以下两种写法都无效 </span>
<span>class</span> <span>Foo</span> <span>{</span>  
    <span>// 写法一  </span>
    <span>prop</span>: <span>2</span>
    <span>// 写法二  </span>
    <span>static</span> <span>prop</span>: <span>2</span>
<span>}</span>
<span>Foo</span><span>.</span><span>prop</span> <span>// undefined</span>

继承

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

<span>class</span> <span>ColorPoint</span> <span>extends</span> <span>Point</span> <span>{</span>  
    <span>constructor</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>,</span> <span>color</span><span>)</span> <span>{</span>    
        <span>super</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>)</span><span>;</span> <span>// 调用父类的constructor(x, y)</span>
        <span>this</span><span>.</span><span>color</span> <span>=</span> <span>color</span><span>;</span>  
    <span>}</span>
 
    <span>toString</span><span>(</span><span>)</span> <span>{</span>    
        <span>return</span> <span>this</span><span>.</span><span>color</span> <span>+</span> <span>' '</span> <span>+</span> <span>super</span><span>.</span><span>toString</span><span>(</span><span>)</span><span>;</span> <span>// 调用父类的toString()  </span>
    <span>}</span>
<span>}</span>
 
 
<span>class</span> <span>Point</span> <span>{</span> <span>/* ... */</span> <span>}</span>
<span>class</span> <span>ColorPoint</span> <span>extends</span> <span>Point</span> <span>{</span>  <span>constructor</span><span>(</span><span>)</span> <span>{</span>  <span>}</span> <span>}</span>
<span>let</span> <span>cp</span> <span>=</span> <span>new</span> <span>ColorPoint</span><span>(</span><span>)</span><span>;</span> <span>// ReferenceError</span>

如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

<span>constructor</span><span>(</span>...<span>args</span><span>)</span> <span>{</span>  <span>super</span><span>(</span>...<span>args</span><span>)</span><span>;</span> <span>}</span>

另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父 类实例加工,只有super方法才能返回父类实例。

<span>class</span> <span>Point</span> <span>{</span>  
    <span>constructor</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>)</span> <span>{</span>    
        <span>this</span><span>.</span><span>x</span> <span>=</span> <span>x</span><span>;</span>    
        <span>this</span><span>.</span><span>y</span> <span>=</span> <span>y</span><span>;</span>  
    <span>}</span>
 
<span>}</span>
<span>class</span> <span>ColorPoint</span> <span>extends</span> <span>Point</span> <span>{</span>
    <span>constructor</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>,</span> <span>color</span><span>)</span> <span>{</span>
        <span>this</span><span>.</span><span>color</span> <span>=</span> <span>color</span><span>;</span> <span>// ReferenceError    </span>
        <span>super</span><span>(</span><span>x</span><span>,</span> <span>y</span><span>)</span><span>;</span>    
        <span>this</span><span>.</span><span>color</span> <span>=</span> <span>color</span><span>;</span> <span>// 正确  </span>
    <span>}</span>
<span>}</span>

Class 与 function 的区别

Class不存在变量提升(hoist),这一点与ES5完全不同。

<span>new</span> <span>Foo</span><span>(</span><span>)</span><span>;</span> <span>// ReferenceError </span>
<span>class</span> <span>Foo</span> <span>{</span><span>}</span>

ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是 先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

常见疑问

私有方法是常见需求,但ES6不提供,只能通过变通方法模拟实现。

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。 考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。

Object.getPrototypeOf方法可以用来从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。

<span>Object</span><span>.</span><span>getPrototypeOf</span><span>(</span><span>ColorPoint</span><span>)</span> <span>===</span> <span>Point</span> <span>// true</span>

new.target属性

new是从构造函数生成实例的命令。ES6为new命令引入了一个new.target属性,(在构造函数中)返回new命令作用于的那个构造函数。如果构造函数 不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。

<span>function</span> <span>Person</span><span>(</span><span>name</span><span>)</span> <span>{</span>  
    <span>if</span> <span>(</span><span>new</span><span>.</span><span>target</span> !== <span>undefined</span><span>)</span> <span>{</span>    
        <span>this</span><span>.</span><span>name</span> <span>=</span> <span>name</span><span>;</span>  
    <span>}</span> <span>else</span> <span>{</span>    
        <span>throw</span> <span>new</span> <span>Error</span><span>(</span><span>'必须使用new生成实例'</span><span>)</span><span>;</span>  
    <span>}</span>
<span>}</span>
<span>// 另一种写法 </span>
<span>function</span> <span>Person</span><span>(</span><span>name</span><span>)</span> <span>{</span>  
    <span>if</span> <span>(</span><span>new</span><span>.</span><span>target</span> <span>===</span> <span>Person</span><span>)</span> <span>{</span>    
        <span>this</span><span>.</span><span>name</span> <span>=</span> <span>name</span><span>;</span>  
    <span>}</span> <span>else</span> <span>{</span>    
        <span>throw</span> <span>new</span> <span>Error</span><span>(</span><span>'必须使用new生成实例'</span><span>)</span><span>;</span>  
    <span>}</span>
<span>}</span>
<span>var</span> <span>person</span> <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>'张三'</span><span>)</span><span>;</span> <span>// 正确 </span>
<span>var</span> <span>notAPerson</span> <span>=</span> <span>Person</span><span>.</span><span>call</span><span>(</span><span>person</span><span>,</span> <span>'张三'</span><span>)</span><span>;</span>  <span>// 报错</span>

需要注意的是,子类继承父类时,new.target会返回子类。

<span>class</span> <span>Rectangle</span> <span>{</span>  
    <span>constructor</span><span>(</span><span>length</span><span>,</span> <span>width</span><span>)</span> <span>{</span>    
        <span>console</span><span>.</span><span>log</span><span>(</span><span>new</span><span>.</span><span>target</span> <span>===</span> <span>Rectangle</span><span>)</span><span>;</span>
        <span>// ...  </span>
    <span>}</span>
<span>}</span>
<span>class</span> <span>Square</span> <span>extends</span> <span>Rectangle</span> <span>{</span>  <span>constructor</span><span>(</span><span>length</span><span>)</span> <span>{</span>    <span>super</span><span>(</span><span>length</span><span>,</span> <span>length</span><span>)</span><span>;</span>  <span>}</span> <span>}</span>
<span>var</span> <span>obj</span> <span>=</span> <span>new</span> <span>Square</span><span>(</span><span>3</span><span>)</span><span>;</span> <span>// 输出 false</span>

利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。

<span>class</span> <span>Shape</span> <span>{</span>  
    <span>constructor</span><span>(</span><span>)</span> <span>{</span>    
        <span>if</span> <span>(</span><span>new</span><span>.</span><span>target</span> <span>===</span> <span>Shape</span><span>)</span> <span>{</span>      
            <span>throw</span> <span>new</span> <span>Error</span><span>(</span><span>'本类不能实例化'</span><span>)</span><span>;</span>    
        <span>}</span>  
    <span>}</span>
<span>}</span>
<span>class</span> <span>Rectangle</span> <span>extends</span> <span>Shape</span> <span>{</span>  
    <span>constructor</span><span>(</span><span>length</span><span>,</span> <span>width</span><span>)</span> <span>{</span>    
        <span>super</span><span>(</span><span>)</span><span>;</span>    <span>// ...  </span>
    <span>}</span>
<span>}</span>
<span>var</span> <span>x</span> <span>=</span> <span>new</span> <span>Shape</span><span>(</span><span>)</span><span>;</span>  <span>// 报错 var y = new Rectangle(3, 4);  // 正确</span>

多继承的实现

<span>function</span> <span>mix</span><span>(</span>...<span>mixins</span><span>)</span> <span>{</span>  
    <span>class</span> <span>Mix</span> <span>{</span><span>}</span>
    <span>for</span> <span>(</span><span>let</span> <span>mixin</span> <span>of</span> <span>mixins</span><span>)</span> <span>{</span>    
        <span>copyProperties</span><span>(</span><span>Mix</span><span>,</span> <span>mixin</span><span>)</span><span>;</span>    
        <span>copyProperties</span><span>(</span><span>Mix</span><span>.</span><span>prototype</span><span>,</span> <span>mixin</span><span>.</span><span>prototype</span><span>)</span><span>;</span>  
    <span>}</span>
    <span>return</span> <span>Mix</span><span>;</span>
<span>}</span>
<span>function</span> <span>copyProperties</span><span>(</span><span>target</span><span>,</span> <span>source</span><span>)</span> <span>{</span>  
    <span>for</span> <span>(</span><span>let</span> <span>key</span> <span>of</span> <span>Reflect</span><span>.</span><span>ownKeys</span><span>(</span><span>source</span><span>)</span><span>)</span> <span>{</span>    
        <span>if</span> <span>(</span> <span>key</span> !== <span>"constructor"</span>      
        <span>&&</span> <span>key</span> !== <span>"prototype"</span>      
        <span>&&</span> <span>key</span> !== <span>"name"</span><span>)</span> <span>{</span>      
            <span>let</span> <span>desc</span> <span>=</span> <span>Object</span><span>.</span><span>getOwnPropertyDescriptor</span><span>(</span><span>source</span><span>,</span> <span>key</span><span>)</span><span>;</span>      
            <span>Object</span><span>.</span><span>defineProperty</span><span>(</span><span>target</span><span>,</span> <span>key</span><span>,</span> <span>desc</span><span>)</span><span>;</span>    
        <span>}</span>  
    <span>}</span>
<span>}</span>
前端扫地生
我还没有学会写个人说明!
下一篇

K8s炼气期(一)| minikube安装本地Kubenetes环境

你也可能喜欢

评论已经被关闭。

插入图片