javascript 类、 构造函数、继承

626 阅读5分钟

定义类的方法
  • 工厂方法
function creatPerson(name, age) {
            
    var obj = new Object();

    obj.name = name;
    obj.age = age;

    obj.sayName = function() {
        window.alert(this.name);
    };
            
    return obj;
}
  • 构造函数方法
function Person(name, age) {

    this.name = name;
    this.age = age;

    this.sayName = function() {
        window.alert(this.name);
    };
}
  • 原型方法
function Person() {
        
}
        
Person.prototype = {
    constructor : Person,
    name : "Ning",
    age : "23",
    sayName : function() {
        window.alert(this.name);
    }
};
  • 使用构造函数和原型方法(使用最广)
function Person(name, age) {
    this.name = name;
    this.age = age;
}
        
Person.prototype = {
    constructor : Person, 
    sayName : function() {
        window.alert(this.name);
    }
};

构造函数

构造函数定义

定义:构造函数就是用new关键字创建对象时调用的函数

  • 函数名首字母大写,用来区分普通函数
  • 通过this来给对象添加属性和方法
  • 使用new生成实例对象
使用构造函数的场景

在使用对象字面量创建一系列同一类型的对象时,这些对象可能具有一些相似的特征(属性)和行为(方法),此时会产生很多重复的代码,而使用构造函数就可以实现代码复用

完整的构造函数的例子
// 创建一个构造函数
// 首字母大写
function Person(name){
    this.name = name // 自定义属性
    this.sayName = function(){ // 自定义方法
         return this.name
    }
}
// 实例化一个对象
var child = new Person('tom')
console.log(child) // [object]
console.log(child.name)  // tom
console.log(child.constructor === Person// true
console.log(child instanceof Person)  // true,

constructor的作用是返回一个函数对象,该函数由数组对象的原始创建。
instanceof的作用检测左侧的__proto__原型链上,是否存在右侧的prototype原型
构造函数执行的过程
  • 当以new关键字调用时,会创建一个新的内存空间,标记为XXX的实例
  • 函数体内部的this指向该内存
  • 执行函数体内的代码,给this添加属性,就相当于给实例添加属性
  • 默认返回this
构造函数与普通函数的区别
  • 有new与无new的差别
  • 构造函数也是一个普通函数,创建方式和普通函数一样,但构造函数习惯上首字母大写
  • 调用方式不一样。作用也不一样(构造函数用来新建实例对象)
    • 普通函数的调用方式:直接调用 person();
    • 构造函数的调用方式:需要使用new关键字来调用 new Person();
  • 构造函数的函数名与类名相同:Person( ) 这个构造函数,Person 既是函数名,也是这个对象的类名
  • 内部用this 来构造属性和方法
  • 用instanceof 可以检查一个对象是否是一个类的实例,是则返回true。
构造函数的优缺点
  • 优点就是能够通过instanceof识别对象。
  • 缺点是每次实例化一个对象,都会把属性和方法复制一遍。
var child1 = new Person("tom")
var child2 = new Person("kat")
console.log(child1.sayName === child2.sayName)  // false

解决方案:通过原型(prototype)对象,把方法写在构造函数的原型对象上。

function Person (name){
    this.name = name
}
Person.prototype.sayName = function(){
    return this.name
}
var child1 = new Person("tom")
var child2 = new Person("kat")

console.log(child1.sayName === child2.sayName) // true
多个方法时,直接使用一个对象字面形式替换原型对象
Person.prototype = {
    constructor :Person,
    sayName:function(){
        return this.name;
    },
    sayAge:function(){
        return this.age
    }
}

继承

父类有若干属性和方法,子类也有若干属性和方法,让多个子类拥有父类的属性和方法且相互之间不产生影响,这就是继承。

构造函数实现继承

原理:通过call实现的继承本质是改变了this指向,让父类里面的this指到子类的上下文,这样在父类里面通过this设置的属性或者方法会被写到子类上面。

function fun() {
  this.name = 'fun'
}
fun.prototype.myLog = function() {
   console.log(1)
}
 
function obj() {
  fun.call(this)
  this.type = 'child'
}
var O = new obj
console.log(O.myLog)   // undefined

缺点:只能继承父类构造函数上的属性和方法,不能继承父类原型上的属性和方法。

通过原型链实现继承

原理:利用原型链向上查找的机制实现继承,给 obj.prototype 赋值为父类的一个实例,当把obj作为构造函数在它的实例O1上查找属性时查找顺序依次是 O1本身 -> obj.prototype(fun实例)-> fun.prototype 这样既能继承父类构造函数上的属性。也能继承父类原型上的属性。

function fun() {
  this.name = 'fun'
  this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }

function obj(type) {
  this.type = type
}
obj.prototype = new fun()

var O1 = new obj('o1')
var O2 = new obj('o2')

O1.name = 'is O1'
O1.arr.push('123')

console.log(O1.myLog) // 可以继承原型上的属性和方法
console.log(O2.name)  // fun
console.log(O2.arr)   // [1, 2, 3, '123']

构造函数+原型链 实现继承

原理:通过fun.call(this)改变上下文this指向,父类构造函数上的属性和方法设置到了子类上,相互独立避免影响;通过 obj.prototype = new fun() 实现了继承父类原型上的属性和方法。

function fun() {
  this.name = 'fun'
  this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }

function obj () {
  fun.call(this)
  this.type = 'obj'
}
obj.prototype = fun.prototype

var O1 = new fun()
var O2 = new obj()

O1 instanceof obj  // true
O2 instanceof obj  // true
(new fun()).__proto__.constructor  // 父类函数
(new obj()).__proto__.constructor  // 父类函数

Object.create 实现继承

原理:通过create函数创建中间对象,把两个对象区分开,因为通过create创建的对象,原型就是create函数的参数。

function fun() {
  this.name = 'fun'
  this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { 
    console.log(1) 
}

function obj() {
  fun.call(this)
  this.type = 'obj'
}
obj.prototype = Object.create(fun.prototype)
obj.prototype.constructor = obj

var O1 = new fun()
var O2 = new obj()

O1 instanceof obj  // false
O2 instanceof obj  // true
(new fun()).__proto__.constructor  // 父类函数 fun()
(new obj()).__proto__.constructor  // 子类函数 obj()

class

class 为构造函数的语法糖,即 class 的本质是 构造函数。class的继承 extends 本质 为构造函数的原型链的继承。

class 定义一个对象

class Student {
  constructor(name) {  // 构造函数
    this.teacher = '王老师'
    this.name = name
  }
  hello() { // 定义在原型对象上的函数
    console.log(`我是${this.name},我的老师是${this.teacher}。`)
  }
}
var xiaoming = new Student('小明')
var xiaohong = new Student('小红')

通过class定义的类需要实例化出对象的时候也需要new,这和前面说的对象都是new出来的相对应,区别在于通过class关键字定义类代码更简洁,避免了挂载prototype这种分散的代码。

class 继承

class Base {
  constructor(name) {
    this.name = name
    this.school = 'xx大学'
    this.course = ['语文', '数学']
    this.teacher = '王老师'
  }
  modifyTeacher(tName) {
    this.teacher = tName
  }
}

class Student extends Base {
  constructor(name) {
    super(name)
    this.time = new Date()
  }
  addCourse(course) {
    this.course.push(course)
  }
}

var xiaoming = new Student('小明')
var xiaohong = new Student('小红')

extends:extends关节字用来继承一个父类,子类拥有父类的属性和方法。(extends表示原型链对象来自Base)。 super():super用来调用父类的构造函数,否则父类的name属性无法正常初始化。