JavaScript面向对象编程

92 阅读3分钟

继承与原型链

当谈到继承时,jsvascript只有一种结构: 对象。每个实例对象(object)都有一个私有属性(称之为__proto__,隐式原型)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(隐式原型__proto__),层层向上,直到一个对象的原型对象为null。根据定义,null没有原型,并作为这个原型链中的最后一个环节。

几乎所有的Javascript中的对象都是位于原型链顶端的Object的实例

构造函数的原理

javascript的构造函数式的面向对象的编程,其实是把每个实例的原型,挂接在构造函数的原型上,同时在生成实例的过程中,运行构造函数,所以通过this关键字就把传入的参数挂在了新生成的实例的属性上

function Person(name,age){
    this.name = name
    this.age = age
}
Person.prototype.greet=function(){
    console.log('hello,this is'+ this.name)
}
const person1 = new Person('lily', 18)
console.log(person1.greet === Person.prototype.greet) // true
console.log(person1.hasOwnProperty('greet')) // false
console.log(Object.getPrototypeOf(person1)===Person.prototype===person1.__proto__) // true

new实际做了什么

如下案例

   function Person(name){
       this.name = name
   }
   const person1 = new Person('lily')

原理代码实现

function newOperator(constructor,arguments){
    // 创建了一个新的空对象
    const obj = {}
    // 把新对象的原型绑到构造函数的原型上
    Object.setPrototype(obj,constructor.prototype)
    // 构造函数被执行,执行过程中this被绑定在新的对象上
    constructor.call(this,arguments)
    // 返回这个新的对象
    return obj
}

深入理解this关键字

对象的属性可能是一个函数,当引擎遇到对象属性是函数的情况,会将函数单独保存在中,然后再将函数的地址赋值给对象属性;而 Javascript 是允许在函数体内引用当前环境的其他变量,那么问题来了,函数可以在不同的运行环境执行,所以我们就需要一种机制,能够在函数内获得当前运行环境,由此诞生了 this ,它的设计目的就是指向函数运行时所在的环境。

如何正确判定 this 指向,待补充

深入理解箭头函数

  1. 函数体内的this对象是定义时所在的对象,而不是使用时所在的对象
  2. 没有this,不可以当作构造函数,所以call,bind,apply也无效
  3. 除了this,agruments、super合new.target也不存在,分别指向外层函数的对应变量
  4. 不可以使用yeild命令,箭头函数不能用作Generator函数

class详解

class是构造函数的语法糖,它的类型就是函数,类本身就指向构造函数

// es5构造函数的实现方式
function Circle(center,radius){
    this.center = center
    this.radius = radius
}
Circle.prototype.getCircleReference(){
    return 2 * Math.Pi * this.radius
}
const circle = new Circle([1,1],10)

// class的实现方式
class Circle{
    constructor(cneter,radius){
        this.center = center
        this.radius = radius
    }
    getCircleReference(){
     return 2 * Math.Pi * this.radius
    }
}
const circle2 = new Circle([1,1],10)
console.log(Object.getprototype(circle2) === Circle.prototype) // true
//在类的实例上调用方法,其实就是调用原型上的方法
console.log(circle2.constructor === Circle.prototype.constructor) // true

class能够很轻松的实现类与类之间的继承,如下

class Shape{
    constructor(x,y){
        this.x = x
        this.y = y
    }
    move(x,y){
        this.x += x
        this.y += y
    }
}
// 用extends实现类之间的继承
class Circle extends Shape {
    constructor(x,y,radius){
        super(x,y)
    }
    caculateArea(){
        return Math.pi * Math.pow(this.radius, 2)
    }
}
const circle = new Circle(0,0,10)
circle.move(1,1)

es5构造函数实现的继承

// 父类
function Shape(x,y){
    this.x = x
    this.y = y
}
Shape.prototype.move = function(x,y) {
    this.x += x
    this.y += y
}
// 子类
function Circle(x,y,radius){
    // 需要调用父类的构造函数,才能实现属性的继承
    Shape.call(this,x,y)
    this.radius = radius
}
// 父类原型上方法的继承
Circle.prototype = Object.create(Shape.prototype)

// 修正Circle.prototype.constructor指针
console.log(Circle.prototype.constructor) // Shape
Circle.prototype.constructor = Circle
console.log(Circle.prototype.constructor) // Circle

// 扩展自己的方法
Circle.prototype.caculateArea = function(){
    return Math.pi * Math.pow(this.radius, 2)
}

如上所示,类的继承方式相比起来简介明了,大大的方便了我们的开发

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

class Shape{
    constructor(x,y){
        this.x = x
        this.y = y
    }
    static showMethod(){
        console.log('shape')
    }
}
Shape.showMethod()  // shape
const circle = new Shape()
circle.showMethod() // circle.showMethod is not a function
// 父类的静态方法可以被子类继承
class Circle extends Shape {
    static showMethod(){
        console.log(supershowMethod() + ' circle')
    }
}
Circle.showMethod() // shape circle