js设计模式(一)面向对象—封装

wakaka北京

前言

最近一直在看typescript,主要的一个目的是为vue3.x做准备。3.x版本尤雨溪大神采用typescript重写底层,具体内容可以查看[[译] 尤雨溪:Vue 3.0 计划-掘金](https://juejin.im/post/684490...)。在typescript里,几乎完全采用ES6语法,其中最为重要的还是class类的使用,学到这块就有点懵逼了。什么?继承还可以这样玩?什么共有私有什么派生类???我之前学了个假js吧???

于是回头来重新梳理学习一下面对对象编程,这一系列文章全部是基于ES5语法规则,别跟我说为啥不直接用ES6,ES6它最终也是编译成ES5运行。
小子只是个前端菜鸟,对编程还没有太深入的了解,只是个代码搬运工。如果文章有什么错误或者是理解上的错误,还请指条明路,感激不尽。

这一系列将以面对对象三个特性封装继承多态为题进行讲解,更好的理解面对对象编程。

面对过程编程和面对对象编程(OOP)

面对过程编程

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

面对对象编程

面对对象编程就是将你的需求抽象成一个对象,针对这个对象分析其特征和动作,这个对象我们称之为

对象基础知识

对象理解

程序员经常会拿对象来开玩笑:没对象?自己new一个呗,要啥样都可以。那到底什么是对象呢?js中有一个说法是万物皆对象对象就是可以拥有属性和方法的一个集合。人,是一个对象,他有吃饭睡觉的属性,有上班赚钱的方法。所有的一切都可以抽象为一个对象,只不过每个对象都属于自己的属性和方法。

什么叫面对对象编程???在js中,有两种编程风格。面对过程编程面对对象编程,我们写轮播图写动画写页面,大部分都是面对过程编程(反正我个人目前是这样)。

对象三大特征

封装继承多态

一个对象被创建,这中间发生什么 | new 关键字做了什么(面试题)

 1、创建一个新的对象,这个对象的类型是 `object`
 2、设置这个新的对象的内部、可访问性和[[prototype]]属性为构造函数(指prototype.construtor所指向的构造函数)中设置的
 3、将步骤1新创建的对象作为this的上下文 
 4、果该函数没有返回对象,则返回this。

原型和原型链

  • 原型

    JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。这个对象的所有属性和方法,都会被构造函数的所拥有。
    prototype作为对象的内部属性,我们一般通过实例化对象的__proto__这个属性进行访问。
    在原型对象中还有一个属性constructor,这个属性对应创建所有指向该原型的实例的构造函数

image.png

  • 原型链

    当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回null。

image.png

function Person(name, age) {
    this.name = name 
    this.age= age

    this.action = function () {
        alert('hello word')
    }

}
var p = new Person('尤雨溪', 18) // 实例化Person这个对象

console.log(p.name) // 尤雨溪

/*
他们之间的关系

    构造函数的原型 == 实例化p.__proto__
    构造函数 == 构造函数原型的constructor
*/

 console.log(Person.prototype === p.__proto__) // true

 console.log(Person.prototype.constructor === Person) // true

console.log(p.constructor === Person) // true

面对对象—封装

什么是封装

封装就是把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。也就是说,封装就是将属性和方法组成一个类的过程就称之为封装。

对象封装的几种形式

对象字面量

  • 优点:代码简单易懂
  • 缺点:创建多个对象会产生大量的代码,编写麻烦,且并没有实例与原型的概念

    // 创建对象
    var  Person = {
      name: "尤雨溪",
      age: "18",
    
      action : function() {
        alert('hello word!');
      }
    }
    Person.name
    Person.action()
    
    
    // 创建一个空对象 给空对象添加方法属性
    var Person = {}
    Person.name = '尤雨溪'
    Person.age=  18
    
    Person.action  = function() {
     alert('hello word!');
    }
    

工厂模式

  • 对象字面量的形式在创建多个对象的时候会产生大量代码,如果我们把创建新对象、添加对象属性、返回对象的过程放到这个函数中,每次需要创建对象的时候调用函数即可。

    function createPerson(name, age) {
    var person = new Object();
    person.name = name;
    person.age = age;
    
    person.action= function() {
      alert('hello word!');
    };
    return person;
    }
    
    //生成实例
    var p = createPerson('尤雨溪', 18);
    var p2 = createPerson('阮一峰',20)
    
    console.log(p.name)
    p.action()
  • 优点:避免创建大量对象时代码的臃肿
  • 缺点: p1与p2之间没有内在联系

构造函数

所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
function Person(name, age) {
    // 通过this来添加属性方法
    this.name = name 
    this.age = age

    this.action = function () {
        alert('hello word')
    }
}
// 实例化对象
var p1 = new Person('尤雨溪', 18)
var p2 = new Person('阮一峰', 20)
上面我们说过,通过new关键字实例化出来的对象,都会有一个constructor属性指向他们的构造函数
console.log(p1.constructor === Person)     // true
console.log(p2.constructor === Person)     // true
  • 优点:实例化对象和构造函数之间存在关联
  • 缺点:浪费内存,构造函数中定义的方法名action一样,但实例化出来的对象名不一样,造成一个内存的浪费。

原型模式

上面我们说过,每个对象被实例化的时候,都会有一个原型prototype属性,这个属性是可以被其他对象继承的。所以我们可以将对象共有的属性方法,放到原型上,这样每次通过new出来的对象,在构造函数中的方法被多次创建。
function Person(name, age) {
    // 通过this来添加属性方法
    this.name = name 
    this.age = age
}

// 将共同拥有的方法挂在到原型上
Person.prototype.action = function () {
        alert('hello wodr')
    }
// 实例化对象
var p1 = new Person('尤雨溪', 18)
var p2 = new Person('阮一峰', 20)

p1.action()
p2.action()

属性和方法的类型

通过原型模式我们可以将方法属性直接放在原型上,所有的实例对象都可以访问这个属性方法,我们称为公有方法/属性。熟悉ES6的朋友知道,在用class定义类的时候,我们可以定义里面属性方法的类型。是保护类型,还是公开类型,还是私有类型,不同类型之间是不能相互访问的。在ES6中我们可以通过public等关键字来定义,那我们ES5是怎样区分他们的一个类型呢?

我之前在学习面对对象这一块的时候没有太过注意这一块内容,现在我们开发也很少注意到这一块内容。其实在快速开发的时期,我们很少注意到这些javascrip基础理论知识,更缺乏对原生js的理解。

在面向对象中,每个属性都有它的一个类型,可以分为共有私有静态三种。

共有属性和方法

从字面量上的意思理解,共有,就是所有对象都有的属性或方法。

function Person(name, age) {
    // 通过this来添加属性方法

    this.name = name  // 共有属性
    this.age = age        
}

// 将共同拥有的方法挂在到原型上
Person.prototype.action = function () {  // 共有方法
    console.log(`${this.name}是干${this.job}的`)
}
// 实例化对象
var p1 = new Person('尤雨溪', '程序员')

p1.action()

从上面代码可以看出,通过this添加的属性或者在原型上添加的属性和方法都是共有的

  • 调用规则: 调用公有方法,我们必需先实例化对象

私有属性和方法

私有属性的理解可以从函数的作用域出发,函数内部定义的变量在函数外部无法访问。我们看下面栗子

function Person(name, job) {
    // 通过this来添加属性方法
    var name = name  // 私有属性
    var job = job

    function action () { // 私有方法
       console.log(`${name}是干${job}的`)
    }
}
// 实例化对象
var p = new Person('尤雨溪',  '程序员')

console.log(p.name)       // undefined
p.action()   //  p.action is not a function 报错

当实例化创建对象的时候,通过var定义的局部变量是无法在外界被访问,他只能在构造函数内部进行使用,并且不能通过this访问,我们吧代码变动一下再看。

function Person(name, job) {
    // 通过this来添加属性方法
    var name = name  // 私有属性
    var job = job

    function action1 () { // 私有方法
       console.log(`${name}是干${job}的`, '通过this调用')
    }
  
    function action2 () { // 私有方法
       console.log(`${name}是干${job}的`, '在内部调用')
    }
    
    // 这样是不能调用的
    this.action1()
    this.name

    action()
}
// 实例化对象
var p = new Person('尤雨溪',  '程序员')    //尤雨溪是干程序员的 在内部调用


  • 调用规则: 对象的私有方法和属性,外部是不可以访问的,只能在内部使用

特权方法

在函数内部通过this创建的属性和方法,在创建对象时,每个对象自身都会拥有一份并且可以在外部可以访问到。通过this创建的属性可以看作对象的共有属性,而通过this创建的方法不但可以访问这些对象的共有属性方法,还能访问自身的私有属性方法,被称之为特权方法

function Person(name, job) {
    // 通过this来添加属性方法
    var name = name  // 私有属性
    this.job = job   // 共有属性

    this.action = function () { // 特权方法
        console.log(`${name}是干${this.job}的`)
    }

}
// 实例化对象
var p = new Person('尤雨溪', '程序员')
console.log(p.name)
p.action()     // 尤雨溪是干程序员的
  • 调用规则:通过实例化,可以访问共有属性方法和私有属性方法

静态属性和方法

共有属性方法和私有属性方法都用通过new创建对象才能访问,而静态属性方法不需要通过new关键字创建,就可以直接访问。


function Person(name, job) {
    var name = name  // 私有属性
    this.job = job   // 共有属性

    this.action = function () { // 特权方法
        console.log(`${name}是干${this.job}的`)
    }

}
Person.attr = '静态属性'
Person.say = function () {
    console.log('大家好,我是静态方法')
}

console.log(Person.attr) // 静态属性
Person.say() // 大家好,我是静态方法

// 实例化对象
var p = new Person('尤雨溪', '程序员')
console.log(p.attr ) // undefined
p.say()     // 报错
  • 调用规则:静态方法无需实例化对象,便可以调用,对象实例不能调用对象的静态方法,只能调用实例自身的静态属性和方法。

静态类

前面我们说过,对象创建有好几种形式,那我们通过字面量的形式创建的对象,它的类型时什么呢?对象字面量形式创建的对象添加的方法只能时静态属性和静态方法,而包含静态属性和静态方法的类叫静态类

var person = {
    name: "尤雨溪",
    age: "18",

    action : function() {
      console.log('hello word!');
    }
}
person.attr = '我是静态属性'
person.say = function () {
    console.log('大家好,我是静态方法')
}

console.log(person.name)
person.action()

person.attr 
person.say()
  • 调用规则:不需要通过new创建对象

相关文章

[[译] 尤雨溪:Vue 3.0 计划-掘金](https://juejin.im/post/684490...)
js面向过程编程与面向对象编程的区别
JS面向对象编程之封装
Javascript 面向对象(共有方法,私有方法,特权方法,静态属性和方法,静态类)示例讲解

阅读 279
4 声望
0 粉丝
0 条评论
4 声望
1 粉丝
宣传栏