# why?

让面向对象的 更加舒服(语法糖)

# hello world

    class User {
      constructor(name) {
        this.name = name ; 
      }
      getName() {
        return this.name;
      }
    }
    let h = new User('hello');
    let w = new User('world');
    console.log(h.getName(), w.getName())

# 类的 内部工作机制:原型操作

## 类(class) 和函数(function ) :属性 - 对比

属性是非常的相似

    class User {} 
    function Hd() {} ;
    console.dir(User) ; 
    console.log(User.prototype.constructor === User)
    console.dir(Hd) ;
    console.log(Hd.prototype.constructor === Hd)

## 类(class) 和函数(function ) :方法 - 对比

类,自动把 方法放在了 原型中 <mark>方便</mark>

    class User {
      show() {} 
    } 
    function Hd() {} ;
    Hd.prototype.show = function() {} ;
    console.dir(User)
    console.dir(Hd)

## 类(class) 和函数(function ) :方法(特征) - 对比

class 的 方法 默认了 是不可遍历的

不需要像 函数那样,要 defineProperty 来改

    class User {
      show() {} 
    } 
    function Admin() {}
    Admin.prototype.show = function() {} ;

    console.log(
      JSON.stringify(
        Object.getOwnPropertyDescriptor(User.prototype, 'show'),
        null,
        2
      )
    )
    console.log(
      JSON.stringify(
        Object.getOwnPropertyDescriptor(Admin.prototype, 'show'),
        null,
        2
      )
    )

## 类(class)和函数(function):构造函数 - 对比

    class User {
      constructor(name) {
        this.name=name;
      }
    } 
    function Hd(name) {
      this.name=name;
    } ;
    let u = new User('aa') ;
    let h = new Hd('bb') ;
    console.dir(u)
    console.dir(h)

# 严格模式 "use strict"

## 函数(function)

    'use strict'
    function show() {
      console.log(this) ;
    }
    function User() {}
    User.prototype.show = function() {
      function test() {
        console.log(this);
      }
      test() ; // window or undefined(严格模式)
      console.log(this);  // user
      show(); // window or undefined(严格模式)
    }
    let u = new User() ;
    u.show();

## 类(class)

可见(下图),(class内部)默认使用 严格模式

    function show() {
      console.log(this);
    }
    class Hd{
      show(){
        function test() {
          console.log(this);
        }
        test(); // undefined 
        console.log(this); // Hd
        show() ; // window or undefined(严格模式)
      }
    }
    let h = new Hd() ;
    h.show();

# 静态属性

分配给构造函数的属性,我们称为静态属性

    function Web(url) {
      this.url = url ;
    }
    Web.url = 'hdcms.com'; //静态属性
    let hd = new Web('houdunren.com') ;
    console.log(hd);
    console.dir(Web) ;

## 类(class)定义 静态属性(static)

下面的写法不是 静态

    class Request {
      host = 'https://www.houdunren.com';
    }
    let obj = new Request() ;
    obj.host = 'http://baidu.com' ; // 实体类修改,不影响class类
    console.log(obj);
    let obj2 = new Request() ;
    console.log(obj2); // 不受影响

属性写在了 实例类内部
两个实体类的 属性互不影响

这时候, 需要用 <mark>static</mark>

    class Request {
      static host = 'https://www.houdunren.com';
    }
    let obj = new Request() ;
    console.log(obj);
    console.dir(Request);

## 无需实体类,直接调用

    class Request {
      static host = 'https://www.houdunren.com';
      api(url) {
        return `${Request.host}/${url}`;
      }
    }
    let obj = new Request() ;
    console.log(obj.api("article"))

# 静态方法

和静态属性类似,

分配给构造函数的方法,我们称为静态方法

    function User() {}
    User.prototype.show = function() { // 原型链上的 show
      console.log('prototype show'); 
    };

    User.show = function() {
      console.log('static show');// 静态方法
    }

    let hd = new User();
    hd.show();
    User.show();
    
    console.log(hd);
    


## 类(class)定义 静态方法(static)

    class User {
      show() {
        console.log('prototype show');
      }
      static show() { // 静态方法
        console.log("static show"); 
      } 
    }
    new User().show();
    User.show() ;
    
    console.dir(User) ; 

## 静态方法应用:new this()

通过静态方法,能给类的创建过程弄个工厂

    class User {
      constructor(name , age) {
        this.name = name ;
        this.age = age ; 
      }
      static create(... args) {
        return new this(... args) ;
      }
    }
    let xj = User.create('aaa', 111) ;
    console.log(xj)

# 案例 - 课程管理类

    const data = [
      {name: 'js', price: 100} ,
      {name: 'mysql', price: 212} ,
      {name: 'vue.js', price: 99} 
    ];
    class Lesson {
      constructor(data) {
        this.modal = data ;
      }
      static createBatch(data) { // 批量创建
        // map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
        // return data.map((item) => { // 可以简化成下面一行
        // return new Lesson(item) ;
        // });
        return data.map(item => new Lesson(item));
      }
      get price() { // 访问器
        return this.modal.price;
      }
      get name() {
        return this.modal.name ;
      }
      static maxPrice(data) { // 取最大
        return data.sort((a,b) => {
          return  b.price-a.price;
        })[0];
      }
      static totalPrice(data) { // 统计
        return data.reduce((previous, current) => {
          return previous+current.price;
        }, 0);
      }
    }
    let lessons = Lesson.createBatch(data) ; 
    console.log(lessons);
    console.log(Lesson.maxPrice(lessons).price); // 取price 最大
    console.log(Lesson.totalPrice(lessons)) ; // 统计 总价格

# 类中使用访问器

访问器是大量使用的特性

可以在获取/设置属性之前 做一些操作

比如,下面的有效性判断

    class Request {
      constructor(host) {
        this.data = {} ; // 不加, host 那里报错
        this.host = host ;
      }
      set host(url) {
        if(!/^https?:\/\//i.test(url)) { // i 忽略大小写
          throw new Error('地址错误') ;
        }
        this.data.host = url ;
      }
      get host() {
        return this.data['host'] ;
      }
    }
    // let hd = new Request('www.baidu.com'); // 这是不行的
    let hd = new Request('http://www.baidu.com')
    console.log(hd.host)
    console.log(hd)

# 保护属性 - Symbol

高内聚,不希望外部随便修改我们属性

    const PROTECT = Symbol();
    class Common {
      constructor() {
        this[PROTECT] = {};
        this.host = 'https://www.baidu.com';
      }
      set host(url) {
        if (!/^https?:\/\//i.test(url)) {s
          throw new Error("异常地址");
        }
        this[PROTECT].host = url;
      }
      get host() {
        return this[PROTECT].host;
      }
    }
    class User extends Common {
      constructor(name) {
        super();
        this[PROTECT].name = name ; 
      }
      get name() {
        return this[PROTECT].name;
      }
    }
    let u = new User('bilibili');
    console.log(u);
    // u.host = 'www.baidu.com' ; // 无法插入
    u.host = 'https://www.bilibili.com';
    console.log(u.host) ;
    console.log(u.name) ;
    console.log(u);

# 保护属性 - WeakMap

为什么
Symbol 可以实现功能,但是不优雅。 😛

怎么用?
把属性数据 放在第三方的 内存里面
必须获得 第三方内存 才能修改数据

为什么是 WeakMap
因为 WeakMap 是弱引用,随着 键的 其他强引用消失,对应的 value 值也会释放,避免了内存泄漏

## 设置单个值

    const host = new WeakMap() ;
    class User {
      constructor(name) {
        this.name = name ; 
        host.set(this, 'https://www.baidu.com') ;
      }
      set host(url) {
        if(!/^https?:\/\//i.test(url)) {
          throw new Error("网址异常")
        }
        host.set(this, url) ;
      }
      get host() {
        return host.get(this);
      }
    }

    let hd = new User('后盾热') ;
    hd.host = 'http://www.e-handtai.com'
    console.log(hd) ;
    console.log(hd.host)

## 设置多个值的情况

    const protecteds = new WeakMap() ;
    class User {
      constructor(name) {
        this.name = name ; 
        protecteds.set(this,{
          host: 'https://www.baidu.com',
          port: 80
        }) ;
      }
      set host(url) {
        if(!/^https?:\/\//i.test(url)) {
          throw new Error("网址异常")
        }
        protecteds.get(this).host = url ;
      }
      get host() {
        return protecteds.get(this).host;
      }
      get port() {
        return protecteds.get(this).port;
      }
    }
    let hd = new User('后盾热') ;
    hd.host = 'http://www.e-handtai.com'
    console.log(hd) ;
    console.log(hd.host, hd.port) ;

# 私有属性/方法 #

## 使用

普通定义 属性 、方法(方法要用函数)

在方法前加 # 即为 私有

    class User{ 
      #id = 1 ; 
    }
    let u = new User() ;
    u.#id ; 

    class User{ 
      #fun = function() {} ;
    }
    let u = new User() ;
    u.#fun();

## 修改

只能在 内部 进行修改

    class User{ 
      #id = 1 ; 
      set id(id) {
        this.#id = id ;
      }
      get id() {
        return this.#id ;
      }
    }
    let u = new User() ;
    u.id = 2 ; 
    console.log(u.id)
    console.log(u)

    class User{ 
      constructor() {
        this.#func() ;
      }
      #func = function() {
        console.log("private function") ;
      }
    }
    let u = new User() ;

## 无法继承

    class Common {
      #func = function() {} ;
    }
    class User extends Common{ 
      constructor() {
        this.#func() ;
      }
    }
    let u = new User() ;

    class Common {
      #id = 1 ; 
    }
    class User extends Common{ 
      constructor() {
        this.#id; 
      }
    }
    let u = new User() 


# 继承

视频:https://www.bilibili.com/video/av79397327?p=17

## super 的必要性

先看一种情况:
<mark>不用 super 调用 上级方法</mark>

    let user = {
      name: 'user' ,
      show() {
        console.log(this.name)
      }
    }
    let old = {
      __proto__ : user ,
      name: 'old',
      show() {
        this.__proto__.show.call(this);
      }
    }
    old.show() ;


这样看着就麻烦。
更重要的是,当要调用上上级的方法时,是无法做到的 (看下面)

    let common = {
      name: 'common' ,
      show() {
        console.log(this.name)
      }
    }

    let user = {
      __proto__: common ,
      name: 'user' ,
      show() {
        this.__proto__.show.call(this)
      }
    }
    let old = {
      __proto__ : user ,
      name: 'old',
      show() {
        this.__proto__.show.call(this);
      }
    }
    old.show() ;

为什么?

<mark>因为,User show 中的 this.__proto__ 理应指向 common 。但实际指回了user</mark>

所以就导致了 循环调用

用 super 会变得很简单

    let common = {
      name: 'common' ,
      show() {
        console.log(this.name)
      }
    }

    let user = {
      __proto__: common ,
      name: 'user' ,
      show() {
        super.show();
      }
    }
    let old = {
      __proto__ : user ,
      name: 'old',
      show() {
        super.show();
      }
    }
    old.show() ;

## super 小案例

    class Common {
      sum() {
        return this.data.reduce((previous, current) => {
          return previous+=current.price;
        }, 0);
      }
    }
    class Controller extends Common{
    }
    class Lesson extends Controller {
      constructor(data) {
        super() ;
        this.data = data ;
      }
      info() {
        return {
          totalPrice: super.sum()  ,
          data: this.data
        }
      }
    }
    let data = [
      {name: 'js', price: 100} ,
      {name: 'mysql', price: 211} ,
      {name: 'vue.js', price: 89} ,
    ] ;

    let hd = new Lesson(data) ;
    console.log(hd.info())

## 方法重写

    class Common {
      constructor(data) {
        this.data = data ;
      }
      sum() {
        return this.data.reduce((previous, current) => {
          return previous+=current.price;
        }, 0);
      }
      getByKey(key) {
        return this.data.filter(item => {
          return item.name.includes(key) ;
        });
      }
    }
    class Lesson extends Common {
      constructor(data) {
        super(data) ;
      }
      getByKey(key) {
        return super.getByKey(key).map((item) => {
          return item.name;
        });
      }
    }
    let data = [
      {name: 'js', price: 100} ,
      {name: 'mysql', price: 211} ,
      {name: 'vue.js', price: 89} ,
    ] ;

    let com = new Common(data) ;
    let hd = new Lesson(data) ;
    console.log('parent',com.getByKey('js'))
    console.log('sun',hd.getByKey('js'))

## 静态继承(构造方法的对象的继承)和实例继承原理

class 的实现就原型(语法糖)

那么 静态继承 (下图) 对应的 原型方法时怎么实现的呢?(下下图)

    class User {
      static site = 'houdnren.com'
      static show () {
        console.log(this.site) ;
      }
    }
    class Admin extends User {} ;
    Admin.show() ;
    console.dir(Admin);

可以看到,(上图),静态继承的实现只是把 东西放在了 __proto__ (父类的原型方法)里面

对应着下图

    function User() {} // 既是个函数,也是个对象
    User.site = '后盾人'
    User.show = function() {
      console.log(this.site)
    } ;
    function Admin() {} ;    
    Admin.__proto__ = User ; // 继承
    Admin.show();
    console.dir(Admin)

所以说,class(语法糖) 的格式 实现的功能是一样的,但是看着更加简洁把?

注意:
也正是因为这种特性(静态方法写在 <mark>构造方法的对象内部</mark>)
而实例调用方法,是从原型链里面找(不经过<mark>构造方法的对象内部</mark>)
所以,实例是没办法调用静态(<mark>构造方法的对象内部</mark>)方法的
java 是可以的

    class One {
      static show() {
        console.log('one show static')
      }
    }
    let one = new  One() ;
    one.show();

所以跟 java 不一样,javascript 的<mark>静态继承(构造方法的对象的继承)</mark> 跟 <mark>实例对象的继承</mark> 是分开的(是没联系的)
只是 class 这种语法糖结构,把两步一起做了(让我们觉得看起来是有联系的)

<mark>实例对象的继承</mark> 如下

   function One() {} ;
   One.prototype.show = function() {
     console.log("one show")
   }
   function Two () {} ;
   Two.prototype.__proto__ = One.prototype; // 这是非标准写法,浏览器厂商开发的(这里图个方便,就这样写了)
   let t = new Two() ;
   t.show() ;

class (语法糖)形式,同时做了以下两步

    class User {} 
    class Admin extends User{}; // (语法糖)形式,同时做了以下两步
    console.log(Admin.__proto__ == User)
    console.log(Admin.prototype.__proto__ == User.prototype)

## instanceof

instanceof 判断<mark>后者的原型</mark>是否在<mark>前者对象的原型链</mark>上(实例继承)

    function One() {} ;
    function Two () {} ;
    Two.prototype.__proto__ = One.prototype;
    let t = new Two() ;
    console.log(t instanceof Two) ;
    console.log(t instanceof One) ;

    //能用方法实现吗??

    function checkPrototype(obj, constructor) {
      if(obj.__proto__==null) return false ;
      if(obj.__proto__==constructor.prototype) return true ;
      return checkPrototype(obj.__proto__ , constructor) ; // 递归判断
    }
    console.log(checkPrototype(t, Two))
    console.log(checkPrototype(t, One))

## isPrototypeOf

    function A() {} ;
    A.prototype.show = function() {
        console.log('A show') ;
    }
    function B() {};
    B.prototype.__proto__ = A.prototype; // 继承
    let b = {
      __proto__: B.prototype // 实现
    } 
    b.show();
    console.log(A.prototype.isPrototypeOf(B.prototype))
    console.log(A.prototype === B.prototype.__proto__)
    console.log(A.prototype.isPrototypeOf(b))

    // class 呢?

    class Common {
      show() {
        console.log('common show')
      }
    };
    class User extends Common {} 
    let u = new User() ;
    u.show() ;
    console.log(Common.prototype.isPrototypeOf(User.prototype))
    console.log(Common.prototype === User.prototype.__proto__)
    console.log(Common.prototype.isPrototypeOf(u))

## 继承内置类(内置类功能增强)

继承 Array ,并作出增强

添加 first 、 max 方法

    class Arr extends Array{
      constructor(...args) {
        super()
        args.forEach(item => this.push(item));
      }
      first() {
        return this[0];
      }
      max() {
        return this.sort((a,b) => b-a)[0] ;
      }
    }
    let hd = new Arr(22,1,3,4,5,102); 
    console.log(hd)
    console.log(hd.first())
    console.log(hd.max())

# mixin:“多继承”/接口模式

    // mixin
    // 工具类
    let Tool = {
      max(key) {
        return this.data
        .sort((l,r) => r[key]-l[key])[0] ;
      }
    }
    let Arr = {
      count(key) {
        return this.data.reduce((previous, current) => {
          return previous+=current[key];
        }, 0);
      }
    }
    // 实体类
    class Lesson {
      constructor(lessons) {
        this.lessons = lessons ; 
      }
      get data() {
        return this.lessons;
      }
    };
    // mixin!!
    Object.assign(Lesson.prototype, Tool, Arr)
    // 数据
    const data = [
      {name:'js', price: 100, click: 1},
      {name:'mysql', price: 212, click: 2} ,
      {name:'vue.js', price: 90, click: 3}
    ];
    // 测试
    let hd = new Lesson(data) ;
    console.log('max price:',hd.max('price')) ;
    console.log('total click:', hd.count('click'))

# 综合案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style> div { width: 400px; height: 100px; } div>dt { color: aliceblue; background-color: black; } div>dd { height: inherit; background-color: yellowgreen; } </style>
</head>
<body>
  <div class="slide s1">
    <dt>点击</dt>
    <dd>
      <div>内容1</div>
    </dd>
    <dt>点击</dt>
    <dd>
      <div>内容2</div>
    </dd>
    <dt>点击</dt>
    <dd>
      <div>内容2</div>
    </dd>
  </div>
  <script> class Animation { constructor(el) { this.el = el ; this.timeout = 1 ; this.speed = 1 ; this.isShow = true ; this.defaultHeight = this.height ; } get height(){ return window.getComputedStyle(this.el).height.slice(0,-2)*1 ; // *1 转换为数值类型 } set height(height) { this.el.style.height = height+ 'px' ; } hide(callback) { this.isShow = false ; let id = setInterval(() => { if(this.height<=0) { clearInterval(id) callback && callback() ; return ; } this.height = this.height - this.speed ; }, this.timeout); } show(callback) { this.isShow = true ; let id = setInterval(() => { if(this.height>=this.defaultHeight) { clearInterval(id) ; callback && callback() return ; } this.height = this.height + this.speed ; }, this.timeout); } } class Slide { constructor(el) { this.el = document.querySelector(el) ; this.links = this.el.querySelectorAll('dt'); // 链接 this.panels = [...this.el.querySelectorAll('dd')] .map(item => new Panel(item) ); // 面板 this.bind() ; } bind() { this.links.forEach((element, index) => { element.addEventListener('click', () => { this.action(index) ; }) }); } action(index) { Panel.hideAll(Panel.filter(this.panels, index), ()=> { this.panels[index].show(); }) ; } } class Panel extends Animation{ constructor(el) { super(el) ; } static num = 0 ; static hideAll(items, callback) { if(Panel.num>0) return ; items.forEach(item => { Panel.num++ ; item.hide(() => { Panel.num--; }) }) callback && callback() ; } static filter(items , index) { return items.filter((item, i) => i!=index ) } } let s = new Slide('.s1'); </script>
</body>
</html>