详解javascript面向对象

详解javascript面向对象

1、什么是面向对象

面向对象其实是一种编程思想,其中核心的概念有 类,以及类的实例(对象)

JS本身就是基于面向对象思想开发出来的编程语言,所以在学习和开发JS的时候,也要按照面向对象的思想去处理。在JS中有很多内置类:Number、String、Array、Date…等等,同时也可以创建自定义类

2、构造函数

在es6之前,在JS中可以通过声明一个构造函数来定义类。然后通过new关键字来实例化这个类,其构造函数执行后,返回的结果就是类的一个实例。如下:

function Person(name, age) {
    let total = 0; //上下文的私有变量  和实例对象没有必然的联系
    this.name = name
    this.age = age
    
    // return { a: 1 }  // 如果有直接返回一个引用类型值,那么实例化后的值就是这个
}
const instance = new Person('张三', 18) // { name: '张三', age: 18}
复制代码

new 函数():构造函数执行。它和普通函数的执行还是有一些区别的

  1. 相似点:
  • 一样是把函数执行(传递实参也是一样的)
  • 形成私有上下文
  • 也存在私有变量,然后初始化this
  1. 不同点
  • new 执行,浏览器会在当前上下文中,默认创建一个对象(实例对象)
  • 在初始化this的时候,会把this指向这个实例对象,在构造函数中编写this.xxx = xxx的操作,其实就是给这个实例对象设置私有属性。除了这些,其他的操作和这个实例对象没有直接关系
  • 这个构造函数如果没有返回值,或者返回值的类型是基本数据类型,则最后默认返回的是这个默认创建的实例对象。如果有显式的return一个引用类型值,则最后是以这个显式返回的引用类型为主

3、class

在es6后,可以通过class关键字来定义一个类。但需要注意的是:「es5的构造函数可以通过new关键字执行,执行后返回一个实例对象,构造函数也可以直接执行。但是使用class来定义的类,只能通过new关键字来执行,不能直接执行,否则会报错」

class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}
const instance = new Person('张三', 18) // { name: '张三', age: 18}
复制代码

class和构造函数有什么区别?

  1. class在语法上更贴合面向对象的写法
  2. class实现继承更加易读,易理解
  3. class本质上是普通构造函数的语法糖

4、new的原理和实现

/*
new的原理:使用new关键字执行构造函数时,会经过以下过程
function Fn() {
    1. 创建一个实例对象
    2. 也会像普通函数执行一样执行,
        - 也会创建私有上下文
        - 初始化作用域链
        - 变量提升..等等
        this则执行这个实例对象
    3. 返回值没有或者是基本值,则返回的是实例对象
}
const fn =  new Fn()
*/


function _new(Ctor, ...params) {
    // 1.创建一个实例对象「创建Ctor类的实例:实例.__proto__ -> 类.prototype」 
    
    // 可以直接写 obj.__proto__ = Ctor.prototype。但是在IE上禁止了直接操作__proto__
    
    // Object.creat创建的对象,会让这个对象的__proto__指向传入的值
    // 此时的obj.__proto__ = Ctor.prototype
    // 也就是实例的隐式原型__proto__指向了它构造函数的显式原型prototype
    let obj = Object.create(Ctor.prototype)

    // 2.把函数执行,并把this指向实例对象
    const result = Ctor.call(obj, ...params)

    // 3.处理返回值
    if (result !== null && /^(object|function)$/.test(typeof result)) {
        return result
    }
    return obj
}

function Person(name) {
    this.name = name
}
Person.prototype.sayName = function () {
    console.log('my name is ' + this.name)
}

let p = _new(Person, '张三') // { name: '张三' }
复制代码

5、原型和原型链

js中面向对象的底层处理机制:

  1. 所有的引用类型,都具有对象属性。也即是给对象自由扩展属性
  2. 每一个(除箭头函数外的)函数数据类型,都天生自带一个属性:prototype原型对象(也叫显式原型),其属性值是一个对象。这个原型对象上有一个constructor,属性值是当前构造函数本身
  3. 每一个对象数据类型值,都有一个属性:__proto__原型链属性(也叫隐式原型),这个的属性值指向它的构造函数的 prototype 的属性值

「所以,什么是原型? 」

  1. 原型就是(除箭头函数外的)函数数据类型上的prototyp属性。
  2. 原型的作用就是共享属性和方法

「什么又是原型链呢?」

function Person(name) {
    this.name = name
}
Person.prototype.sayName = function () {
    console.log('my name is ' + this.name)
}
const instance = new Person('张三') // { name: '张三' }

// 实例化后打印出来是没有sayName这个方法的,但是却可以调用,这是为什么呢?
instance.sayName() // my name is 张三
复制代码

原型链.png

原型链是一种机制,指的是js对象包括函数对象的显式原型对象(prototype)都内置了一个隐式原型__proto__,这个隐式原型对象指向它的构造函数的显式原型prototype。

对象成员的访问(不管是遍历还是直接访问):

  • 首先会找自己的私有属性。如果私有属性中找不到,则默认会基于__proto__找所属类prototype上的属性或方法。如果还没有,则还会基于prototype上的__proto__继续向上查找。 … 直到找到Object.prototype为止。这个查找机制,就是原型链

6、instanceof原理和实现

instanceof检测的原理:

  1. 构造函数 Symbol.hasInstance 属性方法
  2. 检测构造函数的prototype是否出现在实例的__proto__上
  3. 不能检测基本数据类型,检测的实例必须都是对象。可以想办法把基本数据类型也处理了
function instance_of(example, classFunc) {
    // 参数初始化
    if (typeof classFunc !== "function") throw new TypeError("Right-hand side of 'instanceof' is not callable")
    if (example == null) return false

    // 支持Symbol的并且拥有Symbol.hasInstance,以这个处理
    if (typeof Symbol !== "undefined") {
        let hasInstance = classFunc[Symbol.hasInstance]
        if (typeof hasInstance === "function") {
            return hasInstance.call(classFunc, example)
        }
    }
    
    // 不支持的则基于检测原型链来实现
    let prototype = classFunc.prototype,
        proto = Object.getPrototypeOf(example)
    if (!prototype) {
        return false // 没有prototype的函数(例如:箭头函数)直接返回false
    }
    
    while (true) {
        // 找到Object.prototype.__proto__
        if (proto === null) return false
        // 在原型链上找到了类的原型
        if (proto === prototype) return true
        
        // 也可以写proto = proto.__proto__
        // Object.getPrototypeOf(proto) === proto.__proto__
        proto = Object.getPrototypeOf(proto)
    }
}
let res = instance_of([12, 23], Array)
console.log(res) //=> true  

res = instance_of([12, 23], Object)
console.log(res) //=> true  

res = instance_of([12, 23], RegExp)
console.log(res) //=> false  
复制代码

7. 类的继承

面向对象有封装,继承,多态等几个重要的特性:

  1. 封装:类也是一个函数,把一个功能对应的数据和数据操作方法单独封装起来,外部在使用的时候不用关系内部是怎么实现的,仅给外部提供了调用方法,外部不能直接修改或是操作数据。保证了内部数据的安全性。(低耦合高内聚)
  2. 继承:子类可以继承父类的属性以及方法。提高代码的复用性,方便扩展
  3. 多态:
    • 函数重写:子类重写父类上的方法(基于继承来实现的)
    • 函数的重载,由于参数或是返回值的不同,具备了不同的功能的方法。在js中其实没有严格意义上的函数重载,js的重载是在一个方法内,根据传参不同实现不同的功能。

常见的几种继承方法:

7.1 构造函数继承(基于call来实现)

构造函数继承的原理:把父类构造函数的this指向为子类实例化对象引用,从而使父类执行的时候父类里面的私有属性都会被挂载到子类的实例上去

优点:1. 可以在子类构造函数中向父类传参数。2. 父类的公共属性不会被共像

缺点:不能实现完整的继承,方法都在构造函数中定义,只能继承父类的实例属性和方法。子类不能继承父类定义在prototype中的属性以及方法

function Parent(x) {
    this.x = x
}
Parent.prototype.getX = function() { return this.x }

function Child() {
    Parent.call(this, 100) // 重点代码
    this.y = 200
}
let c = new Child()  // { y: 200, x: 100 }
c.getX()  // 100
复制代码

7.2 原型继承

原型继承的原理:根据对象的可扩展的特性,通过修改子类的prototype实现继承,同时弥补构造函数继承中(子类不能继承父类定义在prototype中的属性以及方法)的缺陷。使子类原型对象指向父类的实例以实现继承, 即重写子类的原型

优点:可以复用父类上公共的属性和方法

缺点:父类的属性一改,实例化对象的对应的值也会全部跟着改变,因为原型对象是共用的

function Parent() {
    this.x = 100
}
Parent.prototype.getX = function() { return this.x }

function Child() {
    this.y = 200
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
let c = new Child() // { y: 200 }
c.getX() // 100
复制代码

7.3 寄生式组合继承(基于call + 另类的原型继承)

function Parent(x) {
    this.x = x
}
Parent.prototype.getX = function() { return this.x }

function Child() {
    Parent.call(this, 100)
    this.y = 200
}
// 可以写 Child.prototype.__proto__ = Parent.prototype, 但是在IE上禁止了直接操作__proto__

Child.prototype = Object.create(Parent.prototype)

// 因为上面直接修改了Child的prototype,所以这里手动声明一下constructor
Child.prototype.constructor = Child
let c = new Child() // { y: 200, x: 100 }
c.getX() // 100
复制代码

7.4 ES6中的类和继承

class Parent {
    constructor(x) {
        this.x = x
    }
    getX() {
        return this.x
    }
}

// extends继承,实现的效果和寄生式组合继承类似
class Child extends Parent {
    constructor() {
        super(100// 相当于调用Parent的constructor,并传入100
        
        // 需要在使用this之前首先调用 super(),
        this.y = 200
    }
}

const c = new Child()
c.getX() // 100
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享