class类与继承

前言

在学习 ES6 的 Class 知识之前我们需要先学习 Object.defineProperty 方法,方法详见:本章 「案例」 defineProperty

class

介绍:js是一个脚本语言没有像强语言一样拥有类这样一个概念,在ES6之前创建一个实例对象都使用的是构造函数的方法。ES6引入了class的概念,通过 class 关键字定义类,作为对象的模板。Class可以看作一个语法糖所以他的本质就是一个 function。class模板写法让对象原型的写法更加清晰、更像面向对象编程的语法。

基本语法

语法

  1. 通过class关键声明的类可以认为是构造函数的另一种写法,同样使用 new 关键字创建类实例对象
  2. 在前端开发中我们约定类名首字母大写。类可以匿名也可具名
class App {
    // 这是一个具名类
}

let app = new App() // 创建 App 类的实例对象 

let Demo = class {
    // 这是一个匿名类
}

let demo = new Demo() // 创建 Demo 类的实例对象 
  1. 构造函数的prototype属性在class依然存在
class App {
           sayHello () {
               console.log('你好我是App')
           }

           sayGoodbye () {
               console.log('再见我是App')
           }
       }
 let app = new App()
 
 app.sayHello()   //   '你好我是App'
 app.sayGoodbye()   //  '再见我是App'    
 /*
 等价于
 class App () {}
 App.prototype = { 
           sayHello () {
               console.log('你好我是App')
           }

           sayGoodbye () {
               console.log('再见我是App')
           }
 }
*/

注意

  1. 我们打印上面的App类会发现,类中的所有方法全部都存储在类的prototype对象上面(即类的所有方法都是原型方法)
  2. class支持动态属性
let key = 'sayHello'
class App {
    [key]() {
               console.log('你好我是App')
    }
}
// App.prototype.sayHello()
// 函数也可以使用动态函数名
function [key] () {
    
}
//等价于 function sayHello () { }
  1. 类内部定义的属性都是不可枚举的
 class App {
           sayHello () {
               console.log('你好我是App')
           }

           sayGoodbye () {
               console.log('再见我是App')
           }
       }
     
for(key in App.prototype) { 
    // 这里不会被执行
    console.log(key)
}

console.log(App.prototype)

constructor 方法

概念: constructor方法是类的构造函数,也是类的默认方法。通过new关键字创建类的实例时,会自动调用 constructor 方法 面试题:new关键字到底做了什么

注意

  1. 一个类必须由constructor方法。如果创建class时没有显示定义constructor方法,class会默认的隐式添加一个空的constructor方法
  2. constructor函数会隐式默认的返回当前实例对象(即this)。开发人员可以显示返回另一个对象这样当前class实例化后生成的就是 constructor函数 返回的新对象
class Demo {
            say() {
                console.log(123)
            }
        }


 class App {
           constructor() {
               return new Demo()
           }

           sayHello () {
               console.log('你好我是App', this)
           }

           sayGoodbye () {
               console.log('再见我是App')
           }
       }
     
 let app = new App()

console.log(app) // 这里的app 是 Demo 的实例对象
  1. class 只允许使用new关键字创建实例对象, 而不允许像构造函数一样直接调用。这一特性是他与构造函数的主要区别
class App {
            sayHello() {
                console.log('你好我是App', this)
            }

            sayGoodbye() {
                console.log('再见我是App')
            }
        }

App() // Error  class不可在没有new关键字情况下直接调用

实例属性

概念:通过class塑造的实例对象自身的属性(及this的属性)

语法

  • 方法一: 在构造函数 constructor构造函数中同this设置
 class App {
             constructor(name, age) {
                // 这里的this就是实例对象自身
                this.name = name
                this.age = age 
            }
}

let app = new App('小明', 12)
  • 方法二: 直接写在class中的属性 属性名 = 属性值
class App {
     // 可以将所有的实例属性写在class的顶部,可以直观地看到当前实例有哪些属性
        a = 1
        b = function() {   }          
}
  • 方法三: 使用类的get/set创建实例属性
    概念: 与ES5 一样class可以在内部使用get 和 set 关键字对某个属性设置 get函数和set函数,拦截该属性的存取行为

语法: 下面实现了一个类似Vue数据发生改变对应DOM更新的案例

class ObsVue {

            constructor(selecter, count) {
                this.html = document.getElementById(selecter)
                this._count = count
                this.inital()
                this.html.onclick = this.addCount.bind(this)
            }
            // 设置赋值拦截函数 当给this.count赋值时该方法会被调用
            set count(value) {
                console.log('每次给this.count 赋值都会触发set方法',value)
                this._count = value
                // 更新UI
                this.html.innerHTML = `你点击了我${this.count}次`
            }
            // 设置取值拦截函数 当获取this.count时该方法会被调用
            get count() {
                // 每次获取this.count属性值 都会调用该函数
                // get 方法一定要有return值,他的返回值就是当前属性的值
                return this._count
            }

            inital() {
                this.html.innerHTML = `你点击了我${this.count}次`
            }

            addCount() {
                console.log(this)
                this.count++
            }

        }



let obs = new ObsVue('app', 0)

obs.count = 17

静态方法

概念:不会被实例对象所继承的方法,只能通过class自身调用
语法:使用关键字static 声明

class App {
       test () {
                console.log('原型方法')
       }

       static demo () {
                console.log('静态方法')
       }

}

let app = new App()

console.log(app)

App.demo() // 只能通过类自身调用

注意

  1. 静态方法内部this指向构造函数自身,不是类的实例
  2. 静态可以被子类所继承

静态属性

概念:不会被实例对象继承的属性就是静态属性,在class中只有静态方法没有静态属性。后来有一个提案提出使用static 关键字声明静态属性
语法

 class App {
     static val = '静态属性'
}

console.log(App.val) // '静态属性'

私有属性

概念: 在js中一直不存在真正的私有属性。以往实现私有属性的方法一共有以下几种:

  • 方法一: 属性名前加下划线,表示该属性是一个私有属性,但是这种方法是不保险的。即使加了下划线该属性在外部依然可以调用
class App {
     _val = '私有属性'
}
  • 方法二: 使用模块化,因为模块内部的所有方法都是对外可见的,将私有方法移出class模块。
class App {
    name = '李雷'
 
    sayHello() {
        /* 调用私有方法 */
        bar.call(this)
    }
    
}
// 将私有方法移出
function bar() {
    console.log(this.name)
}
  • 方法三: Symbol
let a = Symbol('val')
class App {
    constructor () {
       this[a] = '私有属性'
    }
}
  • 方法四: ES2020 中提案 用#开头声明私有属性
class App {
          // 私有属性只能在constructor外部声明
           #age = 14

           constructor() {
               this.name = '123'
               this.#age = 18
           }

           sayHello() {
               console.log(this.name, this.#age)
           }

}

let app = new App()

app.sayHello() // "123" 18

app.#age // Error 私有属性不能再外部调用

类的继承

概念: class 可以通过 extend 关键字实现类的继承
语法

 class Animal {
            constructor() {
                this.type = '动物'
            }

            static show() {
                console.log('我是动物的静态方法')
            }

            static val = 10086

            sayHello() {
                console.log('hello')
            }
        }

class Cat extends Animal {

}

let cat = new Cat()

console.log(cat)

console.log(cat.type)

cat.sayHello()

Cat.show()

console.log(Cat.val) 

注意

使用了类的继承的子类在其constructor函数中一定要调用一个super函数,否则新建实例会报错。

因为在类的继承中子类的this对象必须调用父类constructor完成塑造,得到父类相同的属性和方法,然后进行加工返回子类的this,子类的this再加上子类自身的实例属性方法。super其实就是父类的constructor方法。如果不在子类构造函数内部调用super,子类就没有this对象。

注意如果子类没有显示声明constructor函数,ES6会自动隐式创建constructor函数并在其内部调用super方法。如果你的子类constructor中不需要设置实例属性推荐隐式创建constructor函数

class Cat extends Animal {
    constructor() {
           super()    
     }
}

class Cat extends Animal {
   // 不显示声明 等价于上面的代码,
   // 如果constructor中不需要做任何初始化操作 推荐不要显示声明constructor
}

super的属性

概念: super就是父类构造函数,所以super与父类的构造函数接收同样参数,在使用类的继承时一定要记得给super传递参数
语法

class Animal {
            constructor(x,y) {
                this.x = x
                this.y = y
            }
        }
class Cat extends Animal {
            constructor(x,y,detail) {
                super(x,y)
                this.detail = detail
            }
        }

let cat = new Cat(1,2,'cat')

cat.x // 1
cat.y // 2
cat.detail // 'cat'

注意: 使用...语法可以简化 super 传参问题

class Animal {
            constructor(x,y) {
                this.x = x
                this.y = y
            }
        }
class Cat extends Animal {
            constructor(detail, ...props) {
                super(...props)
                this.detail = detail
            }
       }

let cat = new Cat('猫',1,3)
console.log(cat)
cat.detail // 猫
cat.x // 1
cat.y // 3

「拓展」 ES6其他API

Proxy

链接https://developer.mozilla.org/zh-cn/docs/web/javascript/reference/global_objects/proxy

Map

链接https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map

Set

链接https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set

ArrayBuffer

链接https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

合并空运算符

概念: 判断一个值是否为null,如果为null才返回后者
语法:使用??

let name = ""
let age = 0
let test = null

console.log(name || 'Max') // 'Max'
console.log(age || 18) // 18
console.log(test || 18) // 18

console.log(name ?? 'Max') // ""
console.log(age ?? 18) // 0
console.log(test ?? 18) // 18

可选的链接操作

概念: ES2020可以使用? 符号对一些可选属性进行判断,若问指定的可选属性为空则返回undefined。
注意: ?符号只能用在链式调用中.不能以?结尾: data?.artist?.name? 错误

let data = null
/*
// 模拟歌曲api返回的数据
    {
        id:10086
        artist: {
            name: '吴亦凡',
            id: 1000,
            alias: '十万伏特'
        }
    }
*/
// ES2020之前 异步数据 判断其指定属性是否存在
let artist = data && data.artist && data.artist.name
let alias = (data && data.artist && data.artist.alias) || ''
// ES2020
let artist = data?.artist?.name
let alias = data?.artist?.alias ?? ''

p.innerHTML = `${data.artist.name} (${data.artist.alias})`

动态引入

概念: 如果您有一个写满实用程序功能的文件,而且其中某些功能可能很少使用,那么导入其所有依赖项可能只是浪费资源。 现在,我们可以使用async / await在需要时动态导入依赖项。
语法: import('模块路径')

const doMath = async (num1, num2) => {
  if (num1 && num2) {
    const math = await import('./math.js');
    console.log(math.add(5, 10));
  };
};

doMath(4, 2); // 如果函数doMath 从来没有被调用过 './math.js'就永远不会被执行
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • 1. class类 1. 类的由来: JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一...
    最爱喝龙井阅读 251评论 0 0
  • class Animal {//创建Animal类 constructor(species) { //cons...
    livcll学院阅读 379评论 0 0
  • 前言 在之前我们学到的面对对象编程的思想中,我们去解决一个问题去找对象,对象不存在我们便去创造对象; 那么我们之前...
    笑该动人d阅读 319评论 0 1
  • 简介 Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。...
    codeSirCao阅读 252评论 0 0
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,531评论 28 53