JS类的创建和继承总结(含ES6 class类)

1. 类的创建

1.1 ES5中类的创建 (即构造函数的创建)

// 注:ES5中类的创建其实就是构造函数的创建,和函数创建相同,只是函数名首字母一般推荐大写
// 1. 方法一:直接在构造函数中直接定义属性和方法
//    优点:可以传参
//    缺点:每 new 一个实例,就会复制一份构造函数里面的属性和方法,费内存
function Star(name, sex) {
  this.name = name
  this.sex = sex
  this.role = '明星'
  this.singSong = function () {
    console.log('他唱的歌叫' + song)
  }
}
// 2. 方法二:先创建一个构造函数(类),然后在原型上挂载属性和方法
//    优点:继承公共属性或方法的时候不费内存,因为它相当于引用,也就是指向的问题,并非“复制”
//    缺点:定义非公共属性时费内存,而且不高效,即不能够用传参来“定制”属性值
function Star() {}
Star.prototype.name = '刘德华'
Star.prototype.sex = '男'
Star.prototype.role = '明星'
Star.prototype.singSong = function (song) {
  console.log('他唱的歌叫' + song)
}
// 3. 总结及注意:
//    3.1 一般将私有属性(即属性值不同的)定义在构造函数(类)中,公共的方法定义在原型上
//    3.2 当构造函数中的属性或方法和原型上的相同时,构造函数内的优先级高

// 4. 举例:
//    4.1 在构造函数中定义“私有”属性
function Star(name, sex) {
  this.name = name
  this, (sex = sex)
}
//     4.1 在原型上定义公共属性或方法
//    写法1:
Star.prototype.role = '明星'
Star.prototype.singSong = function (song) {
  console.log('他唱的歌叫' + song)
}
//    写法2:
Star.prototype = {
  role: '明星',
  singSong: function (song) {
    console.log('他唱的歌叫' + song)
  }
}

1.2 ES6中类的创建和继承

// 创建类
class Star {
  // 构造方法,固定写法
  constructor(name, sex) {
    this.name = name
    this.sex = sex
    this.role = '明星'
  }
  // 公共方法
  getName(name) {
    console.log('他的名字是' + name)
  }
}

// 子类继承
class LiuDeHua extends Star {
  constructor(name, sex, role) {
    super(name, sex) // super 表示继承父类的属性
    this.role = role // 表示子类自己添加的
  }
  sing(song) {
    // 子类自己的方法
    console.log('我唱了' + song)
  }
}

// 下面测试
let ldh = new LiuDeHua('刘德华', '男', '四大天王')
console.log(ldh.name) // 刘德华
ldh.getName('liudehua') // 他的名字是liudehua
ldh.sing('忘情水') // 我唱了忘情水

 

2. ES5中类的继承

2.1 基于原型链继承

// 1. 定义好的类
function Star(name, sex) {
  this.name = name
  this.sex = sex
}
Star.prototype.role = '明星'
Star.prototype.singSong = function (song) {
  console.log('他唱的歌叫' + song)
}

// --------------------------------------------------------

// 2. 用原型链的方式实现继承
//   2.1 创建一个空的函数(子函数)
function LiuDeHua() {}
//   2.2 让子函数的原型指向构造函数,相当于在原型上 new 一个构造函数
//       让子函数的原型上拥有构造函数的全部属性和方法
LiuDeHua.prototype = new Star('刘德华', '男')
//   2.3 new 一个实例,然后调用子函数(子类)的属性和方法
let ldh = new LiuDeHua()
console.log(ldh.name) // 输出 刘德华
console.log(ldh.role) //输出 明星
ldh.singSong('忘情水') // 输出 忘情水

// 3. 总结
// 3.1 优点:
// (1)简单,子类实例既是子类的实例也是父类的实例
// (2)父类在原型上新增的方法和属性都能被子类获取到

// 3.2 缺点:
// (1)传参不方便,只能在继承的时候传递参数,无法在创建子类实例的时候传入参数
// (2)无法实现多继承(一个子类继承多个父类)
// (3)原型上的属性、方法被所有的实例共享
// 1. 定义好的类
function Star(name, sex) {
  this.name = name
  this.sex = sex
}
Star.prototype.role = '明星'
Star.prototype.singSong = function (song) {
  console.log('他唱的歌叫' + song)
}

// 2. 用原型链的方式实现继承
//   2.1 创建一个空的函数(子函数)
function LiuDeHua() {}
//   2.2 让子函数的原型指向构造函数,相当于在原型上 new 一个构造函数
//       让子函数的原型上拥有构造函数的全部属性和方法
LiuDeHua.prototype = new Star('刘德华', '男')
//   2.3 调用子函数(子类)的属性和方法
console.log(LiuDeHua.prototype.name) // 输出 刘德华
console.log(LiuDeHua.prototype.role) //输出 明星
LiuDeHua.prototype.singSong('忘情水') // 输出 忘情水

// 3. 总结
// 3.1 优点:
// (1)简单,子类实例既是子类的实例也是父类的实例
// (2)父类在原型上新增的方法和属性都能被子类获取到

// 3.2 缺点:
// (1)传参不方便,只能在继承的时候传递参数,无法在创建子类实例的时候传入参数
// (2)无法实现多继承(一个子类继承多个父类)
// (3)原型上的属性、方法被所有的实例共享

2.2 基于构造(.call .apply)继承

// 1. 定义好的 Star 类
function Star(name) {
  this.name = name
}
Star.prototype.role = '明星'

// 2. 定义好的 Star 类
function Sing(song) {
  this.sing = function () {
    console.log('他唱的歌有' + song)
  }
}

// ------------------------------------------

// 3. 用构造继承方式继承,也就是用 .call .apply,未用原型
function Stars(name, song) {
  Star.call(this, name) // 继承 Star 构造函数的属性或方法
  Sing.call(this, song) // 继承 Sing 构造函数的属性或方法
}

// 4. new 一个实例,并调用属性和方法
let LiuDeHua = new Stars('刘德华', '忘情水') // new 实例的时候传参
console.log(LiuDeHua.name) // 输出 刘德华
console.log(LiuDeHua.role) // undefined,也就是不能继承原型上的属性和方法
LiuDeHua.sing() // 输出 忘情水

// 4. 总结:
//    特点:可以实现多继承
//    缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法

2.3 组合继承

 同时使用上面构造继承原型链继承

优点: 1.传参方便,可以在创建实例时传参

            2.可以实现多继承

缺点: 1.无法继承父类原型上的属性和方法 

            2.因为原型上没有方法,所以无法实现函数复用

2.4 寄生组合继承(推荐)

// 1. 定义好的类
function Star(name, sex) {
  this.name = name
  this.sex = sex
}
Star.prototype.role = '明星'
Star.prototype.singSong = function (song) {
  console.log('他唱的歌叫' + song)
}

// 2. 寄生组合继承方式继承
//    2.1 先用构造继承,继承实例上的属性和方法,不继承原型上的
function LiuDeHua(name, sex) {
  // Star.call(this, arguments)// 使用 arguments,输出[Arguments] { '0': '刘德华', '1': '男' }形式
  Star.call(this, name, sex)
}

//    2.2 再用寄生继承,只继承原型上的
(function () {
  let temp = function () {}
  temp.prototype = Star.prototype
  LiuDeHua.prototype = new temp()
})()

// 3. new 实例,调用属性和方法测试
let ldh = new LiuDeHua('刘德华', '男')
console.log(ldh.name) // 刘德华
ldh.singSong('忘情水') // 他唱的歌叫忘情水
 
// 4. 总结:
// 通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的
// 构造的时候,就不会初始化两次实例方法/属性
// 实现麻烦

 

posted @ 2022-05-05 13:16  RHCHIK  阅读(1062)  评论(0编辑  收藏  举报