JavaScript中的面对对象

127 阅读5分钟

前言

在JavaScript中常见的编程方式有两种,函数式编程和面对对象编程;本文通过介绍对象、类、继承等几个方面面来总结JavaScript中的面对对象。

对象

JavaScript创建对象的两种方式

// 使用new关键字创建

var xiaoming = new Object()
xiaoming.name = 'shengming'
xiaoming.job = 'programmer'
xiaoming.coding = function (){
    return this.name + "在写代码"
}

// 使用字面量的方式创建

var xiaoming = {
    name:'shengming',
    job:'programmer',
    coding:function(){
        return this.name + "在写代码"
    }
}

对象属性的精准操作控制
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

Object.defineProperty(obj,prop,desciptor)

// obj要定义属性的对象
// prop要定义或修改的属性的名称;
// descriptor要定义或修改的属性描述符;

// exp
var xiaoming = {}
Object.defineProperty(xiaoming,"height",{
    configurable:true,
    enumerable:true,
    writable:true,
    value:1.7
})
console.log(xiaoming) // {height: 1.7}

其中desciptor(属性描述符)包含两种类型分别为

  • 数据属性描述符:[[Configurable]]/[[Enumerable]]/[[Writable]]/[[Value]]四个特性;
  • 存取属性描述符:: [[Configurable]]/[[Enumerable]]/[[Get]]/[[Set]];

JavaScript的类———构造函数

构造函数:如果一个函数通过new关键字调用它就是构造函数

使用构造函数创建对象

// 构造函数
function Person(name, work) {
    this.name = name
    this.work = work
    this.coding = function () {
        console.log(this.name+'正在coding')
    }
}

// 使用构造函数创建对象
var person1 = new Person("xiaoming",'programmer')
var person2 = new Person("xiaoting",'teacher',)
person1.coding() // xiaoming正在coding
person2.coding() // xiaoting正在coding

new关键字的作用:

  • 在内存中创建一个新的对象(空对象);
  • 这个对象内部的___proto__属性会被赋值为该构造函数的prototype属性;
  • 构造函数内部的this,会指向创建出来的新对象;
  • 执行函数的内部代码;
  • 如果构造函数没有返回非空对象,则返回创建出来的新对象;

构造函数的原型对象:

  • 我们创建的每个函数都有一个prototype(原型)属性
  • 这个属性是一个指针,指向一个对象
  • 而这个对象的作用是存放这个类型创建的所有实例共享的属性和方法。
  • 指向的这个对象, 就是我们的所谓的原型对象.

原型对象的作用:使用原型对象可以让所有对象实例共享它所包含的属性和方法。

原型对象的内存示意图:

image.png

原型链:js 查找变量时首先会在当前的函数中找,如果没有去原型中找,找到就直接返回,没有找到则会沿着_proto_指向的上层原型中找,直到顶层Oject的原型,如果沿途一旦找到则直接返回,如果没找到则返回undefined,这样所组成的一整条链路叫做原型链(自己总结的,可能有描述不够准确的地方)

原型内存示意图

image.png

继承

原型链继承 原型链继承就是让子类的prototype指向父类的实例,来实现原型链继承,具体代码如下:

//父类
function Person(name){
    this.name = name
}
// 给父类添加行为
Person.prototype.say = function (){
    console.log(this.name + '在说话')
}
// 定义子类
function Programmer (company){
    this.company = company
}
// 实现继承
Programmer.prototype = new Person()

// 给子类添加方法
Programmer.prototype.coding = function (){
    console.log(this.name + "正在coding")
}

具体内存示意图如下:

image.png

原型继承的弊端:

  1. 这个属性会被多个对象共享,如果这个属性是一个引用类型,那么就会造成污染;
  2. 由于父类的实例是一次性创建出来的,所以没法传参,也就没法做定制化 组合继承 在原型的基础上增加子类利用call/apply调用父类,以到达传参实例化的作用
//父类
function Person(name){
    this.name = name
}
// 给父类添加行为
Person.prototype.say = function (){
    console.log(this.name + '在说话')
}
// 定义子类
function Programmer (name,company){
    Person.call(this.name)
    this.company = company
}
// 实现继承
Programmer.prototype = new Person()

// 给子类添加方法
Programmer.prototype.coding = function (){
    console.log(this.name + "正在coding")
}

组合继承存在什么问题呢?

1、组合继承最大的问题就是无论在什么情况下,都会调用两次父类构造函数。

  • 一次在创建子类原型的时候;
  • 另一次在子类构造函数内部(也就是每次创建子类实例的时候);

2、所有的子类实例事实上会拥有两份父类的属性

  • 一份在当前的实例自己里面(也就是person本身的),另一份在子类对应的原型对象中,内存浪费

寄生组合式继承 组合式继承最大的问题就是调用构造函数生成实例作为子类prototype的指向,如果有一个对象,不调用构造函数而同样指向构造函数prototype,那问题就解决了;寄生组合式继承代码如下:

//父类
function Person(name){
    this.name = name
}
// 给父类添加行为
Person.prototype.say = function (){
    console.log(this.name + '在说话')
}
// 定义子类
function Programmer (name,company){
    Person.call(this,name)
    this.company = company
}
// 实现继承
function clone (parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程    
    child.prototype = Object.create(parent.prototype);    
    child.prototype.constructor = child;
}

clone(Person,Programmer)

// 给子类添加方法
Programmer.prototype.coding = function (){
    console.log(this.name + "正在coding")
}

其实早期没有Object.create()方法;而是道格拉斯·克罗克福德(DouglasCrockford)提出的解决方案,

  // 定义object函数
function clone(o) {
    function F() {}
    F.prototype = o
    return new F()
}
// 定义寄生式核心函数
function inhreitPrototype(parent, child) {
    var prototype = clone(parent.prototype)
    prototype.constructor = parent
    child.prototype = prototype
}

所以寄生组合式继承早期代码如下

  // 定义object函数
function clone(o) {
    function F() {}
    F.prototype = o
    return new F()
}
// 定义寄生式核心函数
function inhreitPrototype(parent, child) {
    var prototype = clone(parent.prototype)
    prototype.constructor = parent
    child.prototype = prototype
}
//父类
function Person(name){
    this.name = name
}
// 给父类添加行为
Person.prototype.say = function (){
    console.log(this.name + '在说话')
}
// 定义子类
function Programmer (name,company){
    Person.call(this,name)
    this.company = company
}
// 实现继承
// 使用寄生组合式核心函数
inhreitPrototype(ProgrammerPerson)

clone(Person,Programmer)

// 给子类添加方法
Programmer.prototype.coding = function (){
    console.log(this.name + "正在coding")
}

现在我们用到的继承更多的是用ES6的继承,ES6继承本质上是寄生式组合继承的语法糖,通过babel专业之后还是ES5的实现方式,ES6的继承代码如下:

    class Person {
        constructor(name){
            this.name = name
        }
        say = function (){
            console.log(this.name + '在说话')
        }
    }
    
    class Programmer extends Person {
        constructor(name, company) {     
           super(name)    
           this.company = company 
         }
         coding = function (){
            console.log(this.name + "正在coding")
         }
    }