类的简单认识(一)

195 阅读7分钟

一.类的简介

类的由来

  • 在js中,生成实例对象的传统方法是通过构造函数,如下所示
  // ES6之前实例化对象只能使用函数
  function Point(x,y){
   this.x=x;
   this.y=y;
  }
  Point.prototype.toString=function(){
   return '('+this.x+','+this.y+')'
  }
  var one=new Point(1,2)
  console.log(one.toString());//(1,2)
  
  // ES6中可以使用类进行实例化对象
  class Person{
   constructor(x,y) {
       this.x=x;
      this.y=y;
   }
   toString(){
    return '('+this.x+','+this.y+')'
   }
  }
  var two=new Person(3,4)
  console.log(two.toString());//(3,4)
  • 在本质上,类的数据类型就是函数,类本身就是指向自身的构造函数
  • 本质上,类的所有方法都是定义在类的prototype属性上面
  • 也就是说,类的实例的constructor===类的原型的constructor
  class Point{}
  // 1. 类的数据类型是函数
  console.log(typeof Point// function
  // 2. 类的原型链上的构造函数指向类
  console.log(Point===Point.prototype.constructor)  //true 
  // 3. 类的实例可以调用类的方法是因为类的方法相当于定义在类的原型上
  // 所以类的实例通过原型链能够找到方法来调用
  
  // 4. 类的实例的constructor全等于类的原型的constructor
  console.log(new Point().constructor === Point.prototype.constructor//true
  
  // 5. 不能声明同名类。Identifier 'Point' has already been declared
  class Other{
   constructor(name) {
       this.name=name;
   }
   toValue(){
    return this.name+'原型链上'
   }
  }
  var one=new Other('hh')
  console.log(one.toValue()) //hh原型链上
  • 类内部定义的方法默认都是不可枚举的!外部使用原型定义的方法默认是可以枚举的
  // 1.类的内部定义的方法默认是不可枚举的
  class Other{
   constructor() {
   }
   toValue(){
    return this.name+'原型链上'
   }
  }
  // 1. 类的原型链上定义的方法是可以枚举的
  // 必须的啊!因为挂在原型链上,那么可以直接访问到的,除非使用Object.defineProperty设置不可枚举
  Other.prototype.getData=function(){}
  // ["getData"]
  console.log(Object.keys(Other.prototype))

constructor构造器方法

  • constructor方法是类的默认方法,在通过new命令生成对象实例时,会自动调用该方法
  • 一个类是默认具有constructor方法的,即使没有显示声明,但是会默认具有一个空的constructor方法
  • constructor方法默认会返回类的实例对象,但是可以显式使用return返回其他
 // 1.一个没有constructor函数的类
 class One{}
 console.log(new One()) //One
 // 2. 一个具有空构造器constructor函数的类(相当于One)
 class Two{
  constructor(){}
 }
 console.log(new Two()) //Two
 // 3. 一个constructor函数不为空的类
 class Three{
  constructor(){
   console.log('构造器')
  }
 }
 console.log(new Three()) //打印构造器 ,Three
 // 4. 构造器返回其他对象
 class Four{
  constructor(){
   return {name:'a',age:10}
  }
 }
 console.log(new Four()) //{name: "a", age: 10}
  • 类和函数的一个区别在于,类必须使用new调用否则会报错!而函数不需要使用new也可以执行
  • 但是对于类的实例方法,可以直接用类去调用
 class Func{
  static a(){
   return 'ddds'
  }
 }
 console.log(Func.a())

类的实例

  • 类的实例的属性都是定义在原型上的。除非类在构造器声明了!
  class Point{
   constructor(x,y){
    this.x=x;
    this.y=y;
   }
   a(){
    return '定义在原型链上'
   }
  }
  var p=new Point(2,3)
  // 1. x,y属性定义在构造器中,所以实例可以直接得到
  console.log(p.hasOwnProperty('x'))//true
  console.log(p.hasOwnProperty('y'))//true
  // 2. a方法不在构造器中定义,需要通过实例的原型链获取
  console.log(p.hasOwnProperty('a'))//false
  console.log(p.__proto__.hasOwnProperty('a')) //true
  • 在类里面,所有的实例共享一个原型对象
 class Point{}
 var one=new Point()
 var two=new Point()
 // 1. 同一个类new出来的两个实例对象共享一个原型对象
 console.log(one.__proto__===two.__proto__)//true
 // 2. 给一个实例的原型添加方法,另一个实例也会有该方法
 one.__proto__.add=function(){
  return 'add'
 }
 console.log(one.add()) // add
 console.log(two.add()) // add 

getter和setter

  • 在类内部可以使用get和set关键字,对某个属性设置存值函数和和取值函数
  • set和get函数都是设置在函数的Descriptor对象上的
 class one{
  a='使用getter,setter函数'
  get a(){
   return this.a;
  }
  set a(val){
   this.a=val;
  }
 }
 var p=new one()
 console.log(p.a// 使用getter,setter函数
 p.a='改变'
 console.log(p.a// 改变
 
 // 获取对象的Descriptor属性
 var desc=Object.getOwnPropertyDescriptor(one.prototype,'a')
 console.log(desc) //{enumerable: false, configurable: true, get: ƒ, set: ƒ}
 console.log('get' in desc) // true 
 console.log('set' in desc) // true

class表达式

  • class类也可以使用表达式的形式定义,需要注意的是:
 var outer=class inner{
  getName(){
   // return this;  //inner {}
   return inner.name;
  }
 }
 // 1.outer是类在外部的名称,在类的外部必须使用该名称
 var c=new outer()
 console.log(c.getName()) // inner
 // 2. inner是类的内部名称,只能在类的内部使用
 // inner.getName() //ReferenceError: inner is not defined
  • 另外还有一种立即执行的class表达式,但是不可取,使用频率太低
    let person=new class{
    constructor(name) {
      this.name=name;
      }
      getName(){
        console.log(this.name)
      }
    }('yiye')
    person.getName() //yiye

注意点

  1. 只要在类或者模块中,那么默认就是严格模式,不需要使用'use strict'来指定运行模式
  2. 类中不存在变量提升(因为必须保证子类在父类的后面!) class one{}; class two extends one{};
  3. 类也具有函数的name属性,可以通过类.name来获取类的名称
  4. 类的 [Symbol.iterator]方法默认就是类被循环时使用的迭代器
 class Point{
  constructor(...args) {
      this.args=args
  }
  *[Symbol.iterator](){
   for(let arg of this.args){
    yield arg;
   }
  }
 }
 var p=new Point(1,3,'d')
 for(var item of p){
  console.log(item); //1 3 d
 }
  1. this的指向需要额外注意,this默认指向类的实例,但是有时会报错
 class one{
  constructor(name) {
      this.name=name;
  }
  getName(){
   return this.name;
  }
 }
 var a=new one('yiye')
 var {getName}=a; // 获取a这个·对象的getName方法
 
 // 因为只解构得到getName这个方法,而这个方法的this在此时指向的是运行环境的this 
 // console.log(getName()) // Cannot read property 'name' of undefined
 //添加name属性,也是报错。
 var name='hh'
 console.log(getName()) // Cannot read property 'name' of undefined
 // 这是因为在类内部使用的是严格模式,所以this实际指向的是undefined
  • 因为超出字数限制,所以分多次发~
  • 注意点:由于之前没有注意类名首字母要大写的规范,所以修改过一次,上面代码如果有问题就看看类名

本文参考阮一峰ES6教程