ES6-class关键字与继承、通过class手写一个简易的jQuery,考虑插件和扩展性

class关键字

js语言中,生成实例对象的传统方法就是通过构造函数:

function Student(name, number) {
	this.name = name
	this.number = number
}
Student.prototype.sayHi = function() {
	console.log('姓名 ' + this.name + ', 学号' + this.number)
}

var xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 学号 100

ES6提供了class(类)这个概念,作为对象的模板。通过class关键字,可以定义类
基本上,ES6的class可以看做知识一个语法糖,它的绝大部分功能,ES5都可以看到,新的class写法让对象原型的写法更加清晰,上面的代码用class改写:

class Student {
  constructor(name, number) {
    this.name = name
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 学号 ${this.number}`)
  }
}

const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 学号 100

class定义了一个Student类,他里面有constructor方法,这就是构造方法;而this关键字则代表实例对象
也就是说,ES5的构造函数Student,对应ES6的Student类的构造方法; Student类除了构造方法,还定义了一个sayHi方法,定义类的方法的时候,方法之间不需要逗号分隔
构造函数的prototype属性,在ES6的类上继续存在,实际上,类的所有方法都定义在类的prototype属性上面;

constructor方法

constructor方法是类的默认方法,通过new 命令生成对象实例时,自动调用该方法,一个类必须有constructor方法,如果没有显示定义,一个空的constructor方法会被默认添加

constructor方法默认返回实例对象(即this)

class Student{}

// 等同于 定义了一个空的类Student,JavaScript 引擎会自动为它添加一个空的constructor方法。

class Student{
	constructor() {}
}

类的实例对象

生成的实例对象的写法,与ES一样都是使用new命令,实例的属性除非显示定义在其本身(即定义在this对象上),否则都是定义在原型上

class Student {
  constructor(name, number) {
    this.name = name
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 学号 ${this.number}`)
  }
}
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 学号 100

xialuo.hasOwnProperty('name') // true
xialuo.hasOwnProperty('number') // true
xialuo.hasOwnProperty('sayHi') // true

name和number都是实例对象Student 自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true,而sayHi是原型对象的属性(因为定义在Student 类上),所以hasOwnProperty()方法返回false,这些都是ES5的行为保持一致;

不存在变量提升

这个和ES5完全不一样

new Student() // Cannot access 'Student' before initialization
class Student{}

Foo类使用在前,定义在后,这样会报错,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。

Class 的取值函数(getter)和存值函数(setter)

与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

class Student {
  constructor(name, number) {
    this.name = name;
    this.number = number;
  }
  get like() {
    return "like";
  }
  set like(value) {
    console.log(`set ${value}`);
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 学号 ${this.number}`);
  }
}
const xialuo = new Student("夏洛", 100);
xialuo.like; // 'like'
xialuo.like = "math"; // 'set math'  'math'

like属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。

class 的静态方法

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

ES6 明确规定,Class 内部只有静态方法,没有静态属性。

class Student{
	static sayHi(){
		console.log('hi')
	}
}
Student.sayHi() // 'hi'
const xialuo = new Studeng()
xialuo.sayHi() // xialuo.sayHi is not a function

Student类的sayHi方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Student.sayHi()),而不是在Student类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。

父类的静态方法,可以被子类继承。

class People{
  static sayHi() {
    return 'hi';
  }
}

class Student extends People{
}

Student .sayHi() // 'hi'

继承

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

通过extends关键字,子类继承了父类的所有属性和方法

// 父类
class People {
  constructor(name) {
    this.name = name
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
}

// 子类
class Student extends People {
  constructor(name, number) {
    super(name) // 调用父类People的构造函数constructor(name)
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 学号 ${this.number}`)
  }
}

const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 学号 100

super 关键字

super这个关键字,既可以当作函数使用,也可以当作对象使用,

第一情况是:super当作函数调用时,代表父类的构造函数,ES6要求,子类的构造函数必须执行一个super函数;

第二种情况,super作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类;ES6 规定,通过super调用父类的方法时,super会绑定子类的this

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

super虽然代表了父类Father的构造函数,但是返回的是子类Student的实例,即super内部的this指向的是People ,因此super()在这里相当于People .constructor.call(this);
而且作为函数时,super()只能用在子类的构造函数中,在其他地方会报错;

// 父类
class People {
  constructor(name) {
    this.name = name
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
}

// 子类
class Student extends People {
  constructor(name, number) {
    super(name) // 调用父类People的构造函数constructor(name)
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 学号 ${this.number}`)
  }
}

const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 学号 100

// 子类 
class Teacher extends People {
  
  constructor(name, subject) {
    super(name) // 调用父类People的构造函数
    this.subject = subject
  }
  teach () {
    console.log(`${this.name} 教授 ${this.subject}`)
  }
}
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name) // 王老师
console.log(wanglaoshi.subject) // 语文
wanglaoshi.teach() // 王老师 教授 语文

类的prototype属性和proto属性

  • 大多数浏览器的ES5实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性
  • 每个class都有显示原型prototype
  • 每个实例都有隐式原型__proto__
  • 实例的__proto__指向对应class的prototype

基于原型的执行规则

  • 实例获取属性xialuo.name或执行方法xialuo.sayHi()时
  • 先有自身属性和方法中寻找
  • 如果找不到会去__proto__中查找,最终可以去到Object.prototype

class的原型本质

  • class只是ES6语法规范,由ECMA委员会发布
  • ECMA只规定语法规则,即我们代码的书写规范,不规定如何实现
    在这里插入图片描述
// 父类
class People {
  constructor(name) {
    this.name = name
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
}

// 子类
class Student extends People {
  constructor(name, number) {
    super(name) // 调用父类People的构造函数constructor(name)
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 学号 ${this.number}`)
  }
}

const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 学号 100

xialuo.__proto__ === Student.prototype // true
Student.prototype.__proto__ === People.prototype// true
People.prototype.__proto__ === Object.prototype // true
xialuo instanceof Student // true
xialuo instanceof People // true
xialuo instanceof Object // true

通过class手写一个简易的jQuery,考虑插件和扩展性

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>jQuery-test</title>
  </head>
  <body>
    <p>这是个p标签1</p>
    <p>这是个p标签2</p>
    <p>这是个p标签3</p>
    <script>
      class jQuery {
        constructor(selector) {
          const result = document.querySelectorAll(selector);
          const length = result.length;
          for (let i = 0; i < length; i++) {
            this[i] = result[i];
          }
          this.length = length;
          this.selector = selector;
        }
        get(index) {
          return this[index];
        }
        each(fn) {
          for (let i = 0; i < this.length; i++) {
            const elem = this[i];
            fn(elem);
          }
        }
        on(type, fn) {
          return this.each((elem) => {
            elem.addEventListener(type, fn, false);
          });
        }
      }
      // const $p = new jQuery('p')
      // $p.get(1)
      // $p.each((elem) => console.log(elem.nodeName))
      // $p.on('click', () => alert('clicked'))

      // 插件
      jQuery.prototype.dialog = function (info) {
        alert(info);
      };
      // const $p = new jQuery('p')
      // $p.dialog('dialog')

      // "造轮子"
      class myJQuery extends jQuery {
        constructor(selector) {
          super(selector);
        }
        // 扩展自己的方法
        addClass(className) {
          this.each((elem) => elem.classList.add(className));
        }
        style(data) {
          // 相关操作
        }
      }
      // const $p = new myJQuery('p')
      // $p.addClass('test')
    </script>
  </body>
</html>

谢谢你阅读到了最后
期待你,点赞、评论、交流

发布了77 篇原创文章 · 获赞 110 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/weixin_42752574/article/details/105620612