JS基础2

106 阅读16分钟

继承

原型的继承,不是改变构造函数的原型

       function User() {
           this.name = function() {
               console.log('user name methods');
           }
       }
       User.prototype.show = function(){
           console.log('show your name');
       }
       let ds = new User()
       console.log(ds);
       ds.show()


       function Admin(){}
       Admin.prototype = User.prototype
       /*
       设置Admin独有的,然而因为改变了Admin的原型,会导致这个内容保存在User的原型上面
       */
       Admin.prototype.role = function() {
           console.log('获取admin---role');
       }
       let admin = new Admin()
       admin.show()
       admin.role()
       function Member(){}
       Member.prototype = User.prototype
       let mem = new Member()
       mem.role()

image.png

image.png

原型的继承

改变原型对象的原型

    let f = {}
        console.log(f.__proto__);
        console.log('f', Object.getPrototypeOf(f));
        /*
            原型的继承,不是改变构造函数的原型
        */
        function User() {
            this.name = function () {
                console.log('user name methods');
            }
        }
        User.prototype.show = function () {
            console.log('show your name');
        }
        let ds = new User()
        console.log(ds);
        ds.show()


        function Admin() { }
        // Admin.prototype = User.prototype
        Admin.prototype.__proto__ = User.prototype
        Admin.prototype.role = function () {
            console.log('获取admin---role');
        }
        let admin = new Admin()
        admin.show()
        admin.role()
        function Member() { }
        Member.prototype.__proto__ = User.prototype
        Member.prototype.role = function() {
            console.log('member.role');
        }
        let mem = new Member()
        mem.role()

image.png

image.png

上面这种方法,下面的代码顺序没有影响

        Admin.prototype.role = function () {
            console.log('获取admin---role');
        }
        Admin.prototype = Object.create(User.prototype)
  • Object.create()
    • 创建一个对象
    • 使用第一个参数的对象作为对象的原型
        let f = {}
        console.log(f.__proto__);
        console.log('f', Object.getPrototypeOf(f));
        /*
            原型的继承,不是改变构造函数的原型
        */
        function User() {
            this.name = function () {
                console.log('user name methods');
            }
        }
        User.prototype.show = function () {
            console.log('show your name');
        }
        let ds = new User()
        console.log(ds);
        ds.show()


        function Admin() { }


        
        Admin.prototype = Object.create(User.prototype)
        Admin.prototype.role = function () {
            console.log('获取admin---role');
        }
        
        function Member() { }
        Member.prototype.__proto__ = User.prototype
        Member.prototype.role = function() {
            console.log('member.role');
        }
        let mem = new Member()
        mem.role()

        let admin = new Admin()
        admin.show()
        admin.role()

image.png

上面这种方法,下面的代码顺序有影响,因为Object.create创建一个新的,如果把Admin.prototype.role写在Object.create之前,这个是创建在之前的原型上面

        Admin.prototype.role = function () {
            console.log('获取admin---role');
        }
        Admin.prototype = Object.create(User.prototype)

继承对新增对象的影响

        function Admin() { }
        let admin = new Admin()
        admin.role()

构造函数构造完之后,立刻实例化对象,这个时候还没有改变构造函数的原型。对象在使用构造函数在创建的时候,会立刻指向构造函数的原型对象 image.png

        function Admin() { }
        let admin = new Admin()
        admin.role()
        Admin.prototype = Object.create(User.prototype)
        Admin.prototype.role = function () {
            console.log('获取admin---role');
        }

后面改变构造函数的原型,此时构建函数的新对象还是原来的原型,在新原型上面增加的方法就无法使用

image.png

继承对constructor的影响

改变了constructor

    Admin.prototype = Object.create(User.prototype)
    Admin.prototype.role = function () {
      console.log('获取admin---role');
    }
    console.dir(Admin); // constructor: User
    Admin.prototype = Object.create(User.prototype)
    Admin.prototype.constructor = Admin
    Admin.prototype.role = function () {
      console.log('获取admin---role');
    }
    console.dir(Admin);

禁止constructor被遍历

    function User() {
    }
    User.prototype.show = function () {
      console.log('show your name');
    }
    function Admin() { }
    Admin.prototype = Object.create(User.prototype)
    Admin.prototype.constructor = Admin
    
    Admin.prototype.role = function () {
      console.log('获取admin---role');
    }
    console.log(Object.getOwnPropertyDescriptors(Admin.prototype));
    console.dir(Admin)
    let admin = new Admin()
    

image.png

    for (const key in admin) {
      console.log('key', key);
    }

in遍历会将原型链上的也遍历,这个constructor也会被遍历

解决方法:

    function User() {
    }
    User.prototype.show = function () {
      console.log('show your name');
    }
    function Admin() { }
    Admin.prototype = Object.create(User.prototype)
    // Admin.prototype.constructor = Admin
    Object.defineProperty(Admin.prototype, "constructor", {
      value: Admin,
      enumerable: false
    })
    Admin.prototype.role = function () {
      console.log('获取admin---role');
    }
    console.log(Object.getOwnPropertyDescriptors(Admin.prototype));
    console.dir(Admin)
    let admin = new Admin()
    for (const key in admin) {
      console.log('key', key);
    }

image.png

方法重写和父级属性访问

    function User(){}
    User.prototype.show = function(){
      console.log('user.name');
    }
    User.prototype.site = function(){
      return 'https://www.baidu.com'
    }
    function Admin(){}
    Admin.prototype = Object.create(User.prototype)
    Admin.prototype.constructor = Admin
    // 重写show并使用父级属性方法
    Admin.prototype.show = function(){
      console.log(User.prototype.site() + 'admin show');
    }
    let ds = new Admin()
    ds.show() // https://www.baidu.comadmin show

面向对象的多态

    function User(){}
    User.prototype.show = function(){
      console.log(this.description());
    }

    function Admin(){}
    Admin.prototype = Object.create(User.prototype)
    Admin.prototype.description = function(){
      return '管理员在此'
    }

    function Member(){}
    Member.prototype = Object.create(User.prototype)
    Member.prototype.description = function(){
      return '会员来罗'
    }

    function Enterprice(){}
    Enterprice.prototype = Object.create(User.prototype)
    Enterprice.prototype.description = function(){
      return '企业账号'
    }
    for (const key of [new Admin(), new Member(), new Enterprice()]) {
      key.show();
    }

使用父类构造函数初始属性

    function User(name, age){
      this.name = name;
      this.age = age;
      console.log('this', this);
    }
    User.prototype.show = function(){
      console.log('user show');
      console.log('this.name', this.name);
      console.log('this.age', this.age);
    }
    
    // function Admin(name, age){
    //   User.call(this, name, age)
    // }
    function Admin(...args){
      User.apply(this, args)
    }
    Admin.prototype = Object.create(User.prototype)
    let ds = new Admin('东苏', 18)
    ds.show()

image.png

使用原型工厂封装继承

    function extend(sub, sup){
      sub.prototype = Object.create(sup.prototype)
      Object.defineProperty(sub, 'constructor', {
        value: sub,
        enumerable: false
      })
    }
    function User(name, age){
      this.name = name;
      this.age = age;
      console.log('this', this);
    }
    User.prototype.show = function(){
      console.log('user show');
      console.log('this.name', this.name);
      console.log('this.age', this.age);
    }
    
    // function Admin(name, age){
    //   User.call(this, name, age)
    // }
    function Admin(...args){
      User.apply(this, args)
    }
    extend(Admin, User)
    // Admin.prototype = Object.create(User.prototype)
    let ds = new Admin('东苏', 18)
    ds.show()

对象工厂派生对象并实现继承

工厂不断产生对象,需要添加方法就在其添加

    function User (name, age){
      this.name = name;
      this.age = age
    }
    User.prototype.show = function(){
      console.log(this.name, this.age);
    }
    function admin(name, age) {
      // const instance = {}
      // instance.__proto__ = User.prototype
      const instance = Object.create(User.prototype)
      User.call(instance, name, age)
      instance.role = function(){
        console.log('role');
      }
      return instance
    }
    let ds = new admin('东苏', 18)
    ds.role()
    function member (name, age) {
      const instance = Object.create(User.prototype)
      User.call(instance, name, age)
      return instance
    }
    let mem = new member('会员', 23)
    mem.show()

image.png

多继承造成的困扰

多继承一直往上继承

    function extend(sub, sup){
      sub.prototype = Object.create(sup.prototype)
      Object.defineProperty(sub, 'constructor', {
        value: sub,
        enumerable: false
      })
    }
    function Credit(){}
    Credit.prototype.total = function(){
      console.log('积分统计');
    }
    function Request(){}
    extend(Request, Credit)
    Request.prototype.ajax = function(){
      console.log('请求后台');
    }
    function User(name, age){
      this.name = name;
      this.age = age;
      console.log('this', this);
    }
    extend(User, Request)
    User.prototype.show = function(){
      console.log(this.name, this.age);
    }
    function Admin(name, age){
      User.call(this, name, age)
    }
    extend(Admin, User)
    // Admin.prototype = Object.create(User.prototype)
    let ds = new Admin('东苏', 18)
    ds.show()
    ds.ajax()
    ds.total()

image.png

image.png

使用mixin实现多继承

    // mixin混合功能
    function extend(sub, sup) {
      sub.prototype = Object.create(sup.prototype)
      Object.defineProperty(sub, 'constructor', {
        value: sub,
        enumerable: false
      })
    }

    const Address = {
      getAddress() {
        console.log('收获地址');
      }
    }
    const Credit = {
      total() {
        console.log('积分统计');
      }
    }
    const Request = {
      ajax() {
        console.log('请求后台');
      }
    }
    function User(name, age) {
      this.name = name;
      this.age = age;
      console.log('this', this);
    }
    User.prototype.show = function () {
      console.log(this.name, this.age);
    }
    function Admin(name, age) {
      User.call(this, name, age)
    }
    extend(Admin, User)
    Admin.prototype = Object.assign(Admin.prototype, Credit, Request)
    // Admin.prototype = Object.create(User.prototype)
    let ds = new Admin('东苏', 18)
    ds.show()
    ds.ajax()
    ds.total()
    
    function Member(name, age){}
    extend(Member, User)
    Member.prototype = Object.assign(Member.prototype, Address)
    let mem = new Member('成员', 16)
    mem.getAddress()

image.png

使用mixin的内部继承与super关键字

super

当前的原型

    function extend(sub, sup) {
      sub.prototype = Object.create(sup.prototype)
      Object.defineProperty(sub, 'constructor', {
        value: sub,
        enumerable: false
      })
    }

    const Address = {
      getAddress() {
        console.log('收获地址');
      }
    }
    const Request = {
      ajax() {
        console.log( '请求后台');
        return '请求后台'
      }
    }
    const Credit = {
      __proto__: Request,
      total() {
        // console.log(this.__proto__.ajax()+'积分统计');
        // super = this.__proto__
        console.log(super.ajax()+'积分统计');
      }
    }
    
    function User(name, age) {
      this.name = name;
      this.age = age;
      console.log('this', this);
    }
    User.prototype.show = function () {
      console.log(this.name, this.age);
    }
    function Admin(name, age) {
      User.call(this, name, age)
    }
    extend(Admin, User)
    Admin.prototype = Object.assign(Admin.prototype, Credit, Request)
    // Admin.prototype = Object.create(User.prototype)
    let ds = new Admin('东苏', 18)
    ds.show()
    ds.ajax()
    ds.total()
    
    function Member(name, age){}
    extend(Member, User)
    Member.prototype = Object.assign(Member.prototype, Address)
    let mem = new Member('成员', 16)
    mem.getAddress()

image.png

image.png

image.png

声明类

    class User{}
    console.log(typeof User); // function
    let ds = class {}
    console.log(ds);
    class User {
      show() { }
      get() { 
        console.log('东苏来罗');
      }
    }
    let ds = new User()
    ds.get()
    function Article(title) {
      this.title = title
    }
    let a = new Article('小东东')
    console.log(a.title);

    class User {
      constructor(name) {
        this.name = name
      }
      show(){
        console.log(this.name);
      }
    }
    let user = new User('susu')
    console.log(user.name);
    user.show()

类的内部工作机制就是原型操作

    class User{}
    console.log(typeof User); // function
    console.log(User === User.prototype.constructor); // true
    console.dir(User)
    function Ds(){}
    console.log(Ds === Ds.prototype.constructor); // true
    console.dir(Ds)

image.png

    class User{
      // constructor 为对象做属性初始值
      constructor(name){
        this.name = name
      }
      // 方法直接放在prototype上
      show(){}
    }
    console.log(typeof User); // function
    console.dir(User)
    let U = new User('小东');
    console.log(Object.getOwnPropertyNames(U));
    console.log(Object.getOwnPropertyNames(User.prototype));
    console.log('----------------------');
    function Ds(name){
      this.name = name;
    }
    Ds.prototype.show = function(){}
    console.dir(Ds)
    let D = new Ds('小酥')
    console.log(Object.getOwnPropertyNames(D));
    console.log(Object.getOwnPropertyNames(Ds.prototype));

image.png

对象属性的声明

    class User{
      site = '类'
      constructor(name){
        this.name = name
      }
      changeSite (value) {
        this.site = value
      }
      show(){
        return `${this.site}:${this.name}` 
      }
    }
    let ds = new User('东苏')
    console.log(ds);
    ds.changeSite('https://www.baidu.com')
    console.log(ds.show());
    let hd = new User('后盾人')
    console.log(hd);
    console.log(hd.show());

image.png

    constructor(name){
       this.name = name
    }

this关键字是对新对象的声明方式,每个对象声明的属性是它独有的,是对象之间不会共享的

class声明的方法为什么不能遍历

    function Ds(){}
    Ds.prototype.show = function(){}
    console.log(JSON.stringify(Object.getOwnPropertyDescriptor(Ds.prototype, 'show'), null, 2));
    let D = new Ds()
    for (const key in D) {
      // in可以遍历原型链上的内容
      console.log(key);
    }

image.png

    class User {
      constructor(name){
        this.name = name
      }
      show(){}
    }
    let u = new User('小东')
    console.dir(User)
    console.log(JSON.stringify(Object.getOwnPropertyDescriptor(User.prototype, 'show'), null, 2));
    for (const key in u) {
      console.log(key);
    }

image.png

严格模式下运行

普通的函数

    function ds(){
      console.log(this); // 非严格模式下为 window
    }
    ds()
    "use strict"
    function ds(){
      console.log(this); // 严格模式下为 undefined
    }
    ds()

构造函数

    function User(){}
    User.prototype.show = function(){
      function test(){
        console.log(this); // 非严格模式下为 window
      }
      test()
    }
    let u = new User()
    u.show()
    "use strict"
    function User(){}
    User.prototype.show = function(){
      function test(){
        console.log(this); // 严格模式下为 undefined
      }
      test()
    }
    let u = new User()
    u.show()

默认就是严格模式

    class DS { 
      show(){
        function test(){
          console.log('this', this); // undefined
        }
        test()
      }
    }
    let ds = new DS()
    ds.show()

静态属性使用

构造函数

    function Web(url){
      // 对象的属性
      this.url = url
    }
    // 构造函数的属性:静态属性
    Web.url = 'baidu.com'
    let ds = new Web('xiaomi.com')
    console.log(ds);
    console.dir(Web)

    class Request {
      host = "https://www.baidu.com"
    }
    let obj = new Request()
    console.log(obj);
    let obj2 = new Request()
    obj.host = "https://xiaomi.com"
    console.log(obj2);

    console.dir(Request)

image.png

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

    console.dir(Request)

image.png

静态方法的实现原理

通过构造函数创建的对象自动指向原型

__proto__:把函数当对象的原型

this是指向调用方法的对象

    class User {
      constructor(name) {
        this.name = name
      }
      static show() {
        console.log('static show');
      }
    }
    User.prototype.show = function () {
      console.log('prototype show');
    }
    let user = new User('张三')
    console.log(user);
    user.show()
    User.show()
    class User {
      constructor(name, age) {
        this.name = name
        this.age = age
      }
      static create(...args) {
        return new User(...args)
      }
    }
    let user = User.create('zhangsan',18)
    console.log(user.name);
    console.log(user.age);
    class User {
      constructor(name, age) {
        this.name = name
        this.age = age
      }
      static create(...args) {
        return new this(...args)
      }
    }
    let user = User.create('zhangsan',18)
    console.log(user.name);
    console.log(user.age);

静态属性练习之课程管理类

用普通的方式进行对每一条数据进行处理,当内容过多,这种方法比较冗余麻烦

    const data = [
      {
        name: 'js',
        price: 100
      },
      {
        name: 'css',
        price: 80
      },
      {
        name: 'vue',
        price: 200
      }
    ]
    class Lesson {
      constructor(data){
        this.model = data
      }
    }
    let obj1 = new Lesson(data[0])
    console.log(obj1);
    let obj2 = new Lesson(data[1])
    console.log(obj2);

静态属性

    const data = [
      {
        name: 'js',
        price: 100
      },
      {
        name: 'css',
        price: 80
      },
      {
        name: 'vue',
        price: 200
      }
    ]
    class Lesson {
      constructor(data){
        this.model = data
      }
      get name() {
        return this.model.name
      }
      get price(){
        return this.model.price;
      }
      static createBatch(data) {
        return data.map(item => new this(item))
      }
      static maxPrice(data) {
        return data.sort((a, b) => a.price - b.price)[0]
      }
      static totalPrice(data) {
        return data.reduce((pre, cur) => {
          return pre + cur.price
        }, 0)
      }
    }
    let lessons = Lesson.createBatch(data)
    console.log(Lesson.maxPrice(lessons).price);
    console.log(Lesson.totalPrice(lessons));
    console.log(lessons);

在类中使用访问器

    class Request{
      constructor(host){
        this.data = {}
        this.host = host
      }
      set host(url) {
        if (!/^https?:\/\//i.test(url)){
          throw new Error("地址错误")
        }
        this.data.host = url
        /**
            不能直接this.host去赋值,因为这样会不断的触发set
        */
      }
      get host() {
        return this.data['host']
      }
    }
    let ds = new Request('https://baidu.com')
    // ds.host = '123'
    console.log(ds.host);

使用命名规则保护属性

使用下划线特殊符号去命名

    class User {
      _url = 'https://www.houdunren.com'
      constructor(name) {
        this.name = name
      }
      set url(value) {
        if (!/^https?:/i.test(value)){
          throw new Error('错误地址')
        }
        this._url = url
      }
      get url(){
        return this._url
      }
    }
    let user = new User('东苏')
    console.log(user.name);
    // user.url = '123'

上面的例子当使用user._url去做修改的时候,还是可以对其进行修改

公共属性是类的内部外部都可以访问和修改,普通情况下的都是public

使用Symbol定义protected属性

    const HOST = Symbol('主机') // 可以直接修改HOST,但是要注意,编程是模块化的,所以上面的代码会放在一个模块里面,只会把类给外面导出数据,HOST不导出,外部是不可见的
    class User {
      [HOST] = 'https://www.houdunren.com'
      constructor(name) {
        this.name = name
      }
      set url(value) {
        if (!/^https?:/i.test(value)){
          throw new Error('错误地址')
        }
        this[HOST] = url
      }
      get url(){
        return this[HOST]
      }
    }
    let user = new User('东苏')
    console.log(user.name);
    // user.url = '123'
    
    // user[Symbol] = 'https://www.baidu.com'
    console.log(user.url);

受保护的:在类及其子类中可以被引用

    const protecteds = Symbol('主机')
    class Common {
      // [HOST] = 'https://www.houdunren.com'
      constructor(){
        this[protecteds] = {}
        this[protecteds].host = 'https://www.houdunren.com'
      }
      set url(value) {
        if (!/^https?:/i.test(value)){
          throw new Error('错误地址')
        }
        this[protecteds].host = url
      }
      get url(){
        return this[protecteds].host
      }
    }
    class User extends Common{
      
      constructor(name) {
        // 子类调用父类
        super()
        this[protecteds].name = name
      }
      get name(){
        return this[protecteds].name
      }
    }
    let user = new User('东苏')
    console.log(user.name);
    // user.url = '123'
    console.log(user.url);

使用WeakMap保护属性

定义私有变量,私有变量虽然可以直接修改,但是要注意,编程是模块化的,私有变量会放在一个模块里面,只会把类给外面导出数据,私有变量不导出,外部是不可见的。具体意思请看下面的例子

    const host = new WeakMap()
    class User {
      constructor(name) {
        this.name = name;
        host.set(this, "https://houdunren.com")
      }
      set host(url) {
        if(!/^https?:/i.test(url)) {
          throw new Error('错误地址')
        }
        host.set(this, url)
      }
      get host(){
        return host.get(this)
      }
    }
    let user = new User('张三')
    // user.host = '1233'
    host.set(user, "abs") // 这样可以直接修改,但是要注意,编程是模块化的,所以上面的代码会放在一个模块里面,只会把类给外面导出数据,host不导出,外部是不可见的
    console.log(user.name);
    console.log(user.host);

单个属性

    const host = new WeakMap()
    class Common {
      constructor() {
        host.set(this, "https://houdunren.com")
      }
      set host(url) {
        if (!/^https?:/i.test(url)) {
          throw new Error('错误地址')
        }
        host.set(this, url)
      }
      get host() {
        return host.get(this)
      }
    }
    class User extends Common {
      constructor(name) {
        super()
        this.name = name;
      }
    }
    let user = new User('张三')
    // user.host = '1233'
    // host.set(user, "abs") // 这样可以直接修改,但是要注意,编程是模块化的,所以上面的代码会放在一个模块里面,只会把类给外面导出数据,host不导出,外部是不可见的
    console.log(user.name);
    console.log(user.host);
    let da = new User('东苏')
    da.host = "https://www.baidu.com"
    console.log(da.host);

多个属性

    const protecteds = new WeakMap()
    class Common {
      constructor() {
        protecteds.set(this,{
          host:  "https://houdunren.com"
        })
      }
      set host(url) {
        if (!/^https?:/i.test(url)) {
          throw new Error('错误地址')
        }
        host.set(this, {...protecteds.get(this), url})
      }
      get host() {
        return host.get(this)['host']
      }
    }
    class User extends Common {
      constructor(name) {
        super()
        this.name = name;
      }
      set name (name) {
        protecteds.set(this, { ...protecteds.get(this), name})
      }
      get name() {
        return protecteds.get(this)['name']
      }
    }
    let user = new User('张三')
    console.log(user);
    console.log(user.name);
    console.log(protecteds);

private私有属性使用

private: 只有自己本身可以访问和修改

属性前面加#

    class User {
        #host = 'https://www.baidu.com'
        constructor(name){
        this.check(name)
            this.name = name
        }
        set host(url) {
            if(!/^https?:/i.test(url)){
                console.log('错误网址')
            }
            this.#host = url
        }
        /**
            私有方法不可以
            改成私有属性的结构
        */
        // #check(){
        #check = () => {
            if(this.name.length < 5) {
                throw new Error('名字长度不小于五位')
            }
            return true
        }
     }
     let ds = new User('小东')
     ds.host = 'https://www.taobao.com'
     // ds.check()
     ds.#check() // 私有方法,不允许外部访问

class属性继承原理

class语法糖结构,内部实现本质就是原型、原型链

        // function User(name) {
        //     this.name = name
        // }
        // function Admin(name) {
        //     User.call(this, name)
        // }
        // Admin.prototype = Object.create(User.prototype)
        // Admin.prototype.show = function(){}
        // console.dir(Admin)
        // let ds = new Admin('螺蛳粉')
        // console.log(ds);

        class User {
            constructor(name) {
                this.name = name
            }
        }
        class Admin extends User {
            constructor(name) {
                super(name)
            }
        }
        let ds = new Admin('柳州螺蛳粉')
        console.log(ds);

类的方法继承原理

内部实现本质是原型的继承

        class User {
            show(){
                console.log('类的方法');
            }
        }
        class Admin extends User {
            constructor(name){
                super();
                this.name = name
            }
        }
        console.dir(Admin)
        let ds = new Admin('东酥')
        ds.show()

方法在原型链上(所有对象共享),普通属性属于新实例化出来的对象上

super原理分析

        // 模拟
        let hd = {
            name: 'hd-name',
            show() {
                console.log('this', this);
                console.log('hd-show', this.name);
            }
        }
        let xj = {
            name: 'xj-name',
            __proto__: hd,
            show(){
                // this.__proto__.show() // 这个this指向hd
                this.__proto__.show.call(this)
                console.log('this.__proto_', this.__proto__);
                console.log('xj.show');
            }
        }
        xj.show()
        class User {
            constructor(name){
                this.name = name
            }
            show(){
                console.log('user show');
            }
        }
        class Admin extends User {
            constructor(name){
                super()
                this.name = name
            }
            show(){
                super.show()
                console.log('admin show');
            }
        }
        console.dir(Admin)
        let ds = new Admin('dongsu')
        console.log(ds);
        ds.show()

多重继承中super的魅力

上面模拟的代码,在多重继承中就无法实现

    let common = {
            show(){
                console.log('common-show');
            }
        }
        let hd = {
            __proto__: common,
            name: 'hd-name',
            show() {
                console.log('this', this);
                console.log('hd-show', this.name);
                // this.__proto__.show.call(this) // 死循环
            }
        }
        let xj = {
            name: 'xj-name',
            __proto__: hd,
            show(){
                // this.__proto__.show() // 这个this指向hd
                this.__proto__.show.call(this)
                // console.log('this.__proto_', this.__proto__);
                console.log('xj.show');
            }
        }
        xj.show()

在多重继承中使用super

    let common = {
            show(){
                console.log('common-show');
            }
        }
        let hd = {
            __proto__: common,
            name: 'hd-name',
            show() {
                super.show()
                console.log('this', this);
                console.log('hd-show', this.name);
                // this.__proto__.show.call(this) // 死循环
            }
        }
        let xj = {
            name: 'xj-name',
            __proto__: hd,
            show(){
                // this.__proto__.show() // 这个this指向hd
                // this.__proto__.show.call(this)
                // console.log('this.__proto_', this.__proto__);
                super.show()
                console.log('xj.show');
            }
        }
        xj.show()

image.png

constructor中执行super

        function User(name, age){
            this.name = name
            this.age = age
        }
        function Admin(...args){
            User.call(this, ...args)
        }
        Admin.prototype = Object.create(User.prototype)
        let ds = new Admin('东酥', 123)
        console.log(ds);
        class User{
            constructor(name) {
                this.name = name;
            }
        }
        class Admin extends User{
            constructor(){
                super() // 必须写,并且写在子类的this之前
                this.name = 'sb'
            }
        }
        let ds = new Admin('冬冬')
        console.log(ds);

super()在constructor必须写,并且要在子类自定义属性之前。因为如果父类中也有相同名称的属性,后者会覆盖前者,为了避免这种现象,系统直接禁止这种写法

使用super访问父类方法

        class Controller {
            sum() {
                console.log('parent methods');
                return this.data.reduce((pre, cur) => {
                    return pre + cur.price
                }, 0)
            }
        }
        class Lesson extends Controller {
            constructor(data) {
                super() // 未写方法(例如super.sum()),会调用父类的构造函数constructor
                this.data = data
            }
            info() {
                return {
                    totalPrice: super.sum(),
                    data: this.data
                }
            }
        }
        console.dir(Lesson)
        let data = [
            { name: 'js', price: 100 },
            { name: 'mysql', price: 212 },
            { name: 'vue.js', price: 98 }
        ]
        let hd = new Lesson(data)
        hd.sum()
        console.log(hd.info());

方法的重写

        class Common {
            sum() {
                return this.data.reduce((t, c) => t + c.price, 0)
            }
            getByKey(key) {
                return this.data.filter(item => item.name.includes(key))
            }
        }
        class Controller extends Common { }
        class Lesson extends Controller {
            constructor(data) {
                super() // 未写方法,会调用父类的构造函数constructor
                this.data = data
            }
            info() {
                return {
                    totalPrice: super.sum(),
                    data: this.data
                }
            }
            getByKey(key) {
                return super.getByKey(key).map(item => item.name)
            }
        }
        console.dir(Lesson)
        let data = [
            { name: 'js', price: 100 },
            { name: 'mysql', price: 212 },
            { name: 'vue.js', price: 98 }
        ]
        let hd = new Lesson(data)
        hd.sum()
        console.log(hd.info());
        console.log(hd.getByKey('js'));

静态继承原理

image.png

这个prototype是作为构造函数生成实例对象时的,__proto__是本身作为对象的时候使用的

        function User(){}
        User.site = '后盾人'
        User.show = function() {
            console.log('User.static.methods');
        }
        console.dir(User)
        function Admin(){}
        Admin.__proto__ = User;
        console.dir(Admin)
        Admin.show()
        console.log(Admin.site);
        class User {
            static site = 'houdunren.com'
            static show() {
                console.log('User.static.methods');
            }
        }
        class Admin extends User {

        }
        console.dir(Admin)
        Admin.show()
        console.log(Admin.site);

有对象数据就不能用静态方法

内置类继承的原型实现

        function Arr(...args) {
            args.forEach(item => this.push(item))
            this.first = function () {
                return this[0]
            }
            this.max = function () {
                return this.sort((a, b) => b - a)[0]
            }
        }
        Arr.prototype = Object.create(Array.prototype)
        let hd = new Arr(99, 1, 3, 4, 190)
        console.log(hd);

使用继承增强内置类

        class Arr extends Array {
            constructor(...args) {
                super(...args)
            }
            first() {
                return this[0]
            }
            max() {
                return this.sort((a, b) => b - a)[0]
            }
            add(item) {
                this.push(item)
            }
            remove(value) {
                let pos = this.findIndex(item => item === value)
                this.splice(pos, 1)
            }
        }
        let hd = new Arr(99, 1, 3, 4, 190)
        hd.add('好好学习')
        console.log(hd);
        hd.remove('好好学习')
        console.log(hd);
        console.log(hd.first());
        console.log(hd.max());

mixin混合模式使用技巧

        // mixin
        let Tool = {
            max(key){
                console.log('this', this);
                return this.data.sort((a, b)=> b[key] - a[key])[0]
            }
        }
        let Arr = {
            count(key) {
                return this.data.reduce((pre, cur) => pre + cur.price,0)
            }
        }
        class Lesson {
            constructor(lessons) {
                this.lessons = lessons
            }
            get data() {
                return this.lessons
            }
        }
        const data = [
            { name: 'js', price: 100 },
            { name: 'mysql', price: 212 },
            { name: 'vue.js', price: 98 }
        ]
        Object.assign(Lesson.prototype, Tool, Arr)
        console.dir(Lesson);
        let hd = new Lesson(data)
        console.log(hd.max('price'));
        console.log(hd.count('price'));

image.png

灵活的动画处理类

    <style>
        .s1 {
            width: 300px;
            height: 300px;
            background-color: aquamarine;
        }
    </style>
    <div class="s1"></div>
        class Animation {
            constructor(el) {
                this.el = el
                this.timeout = 5
                this.isShow = true
                this.defaultHeight = this.height
            }
            hide(cb) {
                this.isShow = false
                let id = setInterval(() => {
                    console.log('3');
                    if (this.height <= 0) {
                        clearInterval(id)
                        cb && cb()
                        return
                    }
                    this.height = this.height - 1
                }, this.timeout);
            }
            show(cb) {
                this.isShow = false
                let id = setInterval(() => {

                    console.log('3');
                    if (this.height >= this.defaultHeight) {
                        clearInterval(id)
                        cb && cb()
                        return
                    }
                    this.height = this.height + 1
                }, this.timeout);
            }
            get height() {
                return window.getComputedStyle(this.el).height.slice(0, -2) * 1
            }
            set height(height) {
                this.el.style.height = height + 'px'
            }
        }
        // let el = document.querySelector('.s1')
        // let hd = new Animation(el)
        // hd.hide(() => {
        //     console.log('hide完成');
        //     hd.show(() => {
        //         console.log('show完成');
        //     })
        // })

        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。panels就是Panel对象,对象的好处就是可以使用方法
                console.log(this.panels);
                // 绑定事件
                this.bind()
            }
            bind() {
                this.links.forEach((item, i) => {
                    console.log(i);
                    // 批量增加事件
                    item.addEventListener('click', () => {
                        console.log('123', i);
                        this.action(i)
                    })
                });
            }
            action(i) {
                console.log();
                // 点击的展示,其余的隐藏
                Panel.hideAll(Panel.filter(this.panels, i), () => {
                    this.panels[i].show()
                })
            }
        }
        // 批量控制显示隐藏
        class Panel extends Animation {
            static num = 0;
            // 隐藏多个面板
            static hideAll(items, cb) {
                items.forEach(item => {
                    // Panel.num > 0 证明有动画在执行,就不做动画处理
                    if (Panel.num > 0) return;
                    Panel.num++
                    item.hide(() => {
                        Panel.num--
                    })
                    cb && cb()
                })
            }
            // 过滤掉展示的
            static filter(items, i) {
                return items.filter((item, index) => index != i)
            }
        }



        let hd = new Slide('.s1')

模块

模块管理

    let module = (function () {
      //模块列表集合
      const moduleLists = {};
      function define(name, modules, action) {
        modules.map((m, i) => {
          modules[i] = moduleLists[m];
        });
        //执行并保存模块
        moduleLists[name] = action.apply(null, modules);
      }

      return { define };
    })();

    //声明模块不依赖其它模块
    /*
        参数一:模块名字
        参数二:依赖的模块
        参数三:回调函数
    */
    module.define("hd", [], function () {
      return {
        show() {
          console.log("hd module show");
        },
        max(arr, key) {
          return arr.sort((a, b) => b[key] - a[key])[0]
        }
      };
    });
    module.define('lesson', ['hd'], function (hd) {
      let data = [
        { name: 'js', price: 99 },
        { name: 'mysql', price: 88 }
      ]
      console.log(hd.max(data, 'price'))
    })
    //声明模块时依赖其它模块
    module.define("xj", ["hd"], function (hd) {
      hd.show();
    });

模块延迟解析与严格模式

JS从上至下执行

<body>
    <script>
        console.log(document.querySelector('button')); // null
    </script>
    <button>button</button>
</body>

模块后解析

<body>
    <script type="module">
        console.log(document.querySelector('button'));
    </script>
    <button>button</button>
</body>

========>

    <script type="module">
        import {} from './hd.js'
    </script>
    <button>button</button>
    // hd.js
    console.log(document.querySelector('button'));

image.png

模块默认运行在严格模式

    <script type="module">
        import {} from './hd.js'
    </script>
    <button>button</button>
    // hd.js
    site = 'baidu'

image.png

    <script type="module">
        console.log('模块', this); // undefined
        import {} from './hd.js'
    </script>
    <button>button</button>
    <script>
        console.log('非模块', this); // window
    </script>

作用域在模块中的体现

模块有自己的独立作用域

    <script type="module">
        // 模块
        let site = 'baidu'
    </script>
    <script>
        // 顶级作用域
        let url = 'baidu'
    </script>
    <script>
        console.log(url);
        console.log(site);
    </script>

image.png

预解析

模块在导入时只执行一次解析,之后的导入不会再执行模块代码,而使用第一次解析结果,并共享数据。

  • 可以在首次导入时完成一些初始化工作
  • 如果模块内有后台请求,也只执行一次即可

模块的具名导出与导入

ES6使用基于文件的模块,即一个文件一个模块。

  • 使用export 将开发的接口导出
  • 使用import 导入模块接口
  • 使用*可以导入全部模块接口
  • 导出是以引用方式导出,无论是标量还是对象,即模块内部变量发生变化将影响已经导入的变量

有关于模块打包知识请在 后盾人搜索 webpack

导出

    export let site = 'www'
    export function show() {
        return 'show fun'
    }
    export class User {
        static render() {
            return 'user static'
        }
    }
    let site = 'www'
    function show() {
        return 'show fun'
    }
    class User {
        static render() {
            return 'user static'
        }
    }
    export {
        site, 
        show,
        User
    }

导入

模块默认是在顶层静态导入,这是为了分析使用的模块方便打包

    <script type="module">
        import {
            site,
            show,
            User
        } from './hd4.js'
        console.log(site, show(), User.render());
    </script>

批量导入与建议

如果要导入的内容比较多,可以使用 * 来批量导入。

    <script type="module">
        import * as api from './hd4.js'
        console.log(api);
        console.log(api.site);
        console.log(api.show());
        console.log(api.User.render());
    </script>

image.png

建议

因为以下几点,我们更建议使用明确导入方式

  • 使用webpack 构建工具时,没有导入的功能会删除节省文件大小
  • 可以更清晰知道都使用了其他模块的哪些功能

导入导出别名

可以为导入的模块重新命名,下面是为了测试定义的 hd4.js 模块内容。

  • 有些导出的模块命名过长,起别名可以理简洁
  • 本模块与导入模块重名时,可以通过起别名防止错误
    <script type="module">
        import { site as s, show1 } from './hd4.js'
        let site = '我更改了'
        console.log(site);
        console.log(s);
        console.log(show1());
    </script>
let site = 'www'
function show() {
    return 'show fun'
}
class User {
    static render() {
        return 'user static'
    }
}
export {
    site, 
    show as show1,
    User
}

默认导出

默认导出只能有一个,默认导出的功能可以使用任意变量接收

    export default class User {
        static render () {
            return 'static render User'
        }
    }
    class User {
        static render () {
            return 'static render User'
        }
    }
    export { User as default}
    <script type="module">
        import a from './default.js'
        console.log(a.render());
    </script>

混合导入导出的使用

export let site = 'www'
export default class User {
    static render () {
        return 'static render User'
    }
}
let site = 'www'
class User {
    static render () {
        return 'static render User'
    }
}
export { User as default, site}
    <script type="module">
        import de, { site } from './default.js'
        console.log(de.render());
        console.log(site);
    </script>

批量导入混合

    <script type="module">
        import * as api from './default.js'
        console.log(api.default.render());
        console.log(api.site);
    </script>

对于默认导出和命名导出有以下建议

  • 不建议使用默认导出,会让开发者导入时随意命名
  • 如果使用默认导入最好以模块的文件名有关联,会使用代码更易阅读

模块合并导出

// default.js
let site = 'www-default'
class User {
    static render () {
        return 'static render User'
    }
}
export { User as default, site}
// hd4.js
let site = 'www'
function show() {
    return 'show fun'
}
class User {
    static render() {
        return 'user static'
    }
}
export {
    site, 
    show as show1,
    User
}
// m13.js
import * as def from './default.js'
import * as hd4 from './hd4.js'
export {
    def,
    hd4
}

以组的形式导入导出,避免相同命名的冲突

// conbine.js
import * as api from './m13.js'
console.log(api);
console.log(api.def.default.render());
console.log(api.hd4.site);
console.log(api.def.site);
<script type="module" src="./conbine.js"></script>

image.png

按需动态加载模块

使用 import 必须在顶层静态导入模块,而使用import() 函数可以动态导入模块,它返回一个 promise 对象。

    <button>按钮</button>
    <script type="module">
        // import { site } from './default.js'
        // console.log(site);
        document.querySelector('button').addEventListener('click', function () {
            import('./default.js').then(({ site, User }) => {
                console.log(site, User);
            })
        })
    </script>

指令总结

表达式说明
export function show(){}导出函数
export const name='后盾人'导出变量
export class User{}导出类
export default show默认导出
const name = '后盾人' export {name}导出已经存在变量
export {name as hd_name}别名导出
import defaultVar from 'houdunren.js'导入默认导出
import {name,show} from 'a.j'导入命名导出
Import {name as hdName,show} from 'houdunren.js'别名导入
Import * as api from 'houdunren.js'导入全部接口