JavaScript 面向对象编程思想简介

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: JavaScript 面向对象编程思想简介

JavaScript 执行过程


JavaScript 运行分为两个阶段:


  • 预解析


全局预解析(所有变量和函数声明都会提前;同名的函数和变量函数的优先级高)


函数内部预解析(所有的变量、函数和形参都会参与预解析)


    函数


    形参


    普通变量


  • 执行


先预解析全局作用域,然后执行全局作用域中的代码,在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码。


JavaScript 面向对象编程


面向对象介绍


什么是对象


Everything is object (万物皆对象)



对象到底是什么,我们可以从两次层次来理解。


(1) 对象是单个事物的抽象。


一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。


(2) 对象是一个容器,封装了属性(property)和方法(method)。


属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为animal对象,使用“属性”记录具体是那一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。


在实际开发中,对象是一个抽象的概念,可以将其简单理解为:数据集或功能集。


ECMAScript-262 把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。


严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。


提示:每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,也可以是开发人员自定义的类型。


什么是面向对象


面向对象不是新的东西,它只是过程式代码的一种高度封装,目的在于提高代码的开发效率和可维护性。


面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。


它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。


在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。


因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。


面向对象与面向过程:


  • 面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟,有条不紊


  • 面向对象就是找一个对象,指挥得结果


  • 面向对象将执行者转变成指挥者


  • 面向对象不是面向过程的替代,而是面向过程的封装


面向对象的特性:


  • 封装性


  • 继承性


  • [多态性]


扩展阅读:


  • 维基百科 - 面向对象程序设计


  • 知乎:如何用一句话说明什么是面向对象思想?


  • 知乎:什么是面向对象编程思想?


程序中面向对象的基本体现


在 JavaScript 中,所有数据类型都可以视为对象,当然也可以自定义对象。

自定义的对象数据类型就是面向对象中的类( Class )的概念。


我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。


假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个对象表示:


var std1 = { name: 'Michael', score: 98 }
var std2 = { name: 'Bob', score: 81 }


而处理学生成绩可以通过函数实现,比如打印学生的成绩:


function printScore (student) {
  console.log('姓名:' + student.name + '  ' + '成绩:' + student.score)
}


如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是 Student 这种数据类型应该被视为一个对象,这个对象拥有 name 和 score 这两个属性(Property)。


如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个 printScore 消息,让对象自己把自己的数据打印出来。


抽象数据行为模板(Class):


function Student (name, score) {
  this.name = name
  this.score = score
}
Student.prototype.printScore = function () {
  console.log('姓名:' + this.name + '  ' + '成绩:' + this.score)
}


根据模板创建具体实例对象(Instance):


var std1 = new Student('Michael', 98)
var std2 = new Student('Bob', 81)


实例对象具有自己的具体行为(给对象发消息):


std1.printScore() // => 姓名:Michael  成绩:98
std2.printScore() // => 姓名:Bob  成绩 81


面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。


Class 是一种抽象概念,比如我们定义的 Class——Student ,是指学生这个概念,

而实例(Instance)则是一个个具体的 Student ,比如, Michael 和 Bob 是两个具体的 Student 。


所以,面向对象的设计思想是:


  • 抽象出 Class


  • 根据 Class 创建 Instance


  • 指挥 Instance 得结果


面向对象的抽象程度又比函数要高,因为一个 Class 既包含数据,又包含操作数据的方法。


创建对象


简单方式


我们可以直接通过 new Object() 创建:


var person = new Object()
person.name = 'Jack'
person.age = 18
person.sayName = function () {
  console.log(this.name)
}


每次创建通过 new Object() 比较麻烦,所以可以通过它的简写形式对象字面量来创建:


var person = {
  name: 'Jack',
  age: 18,
  sayName: function () {
    console.log(this.name)
  }
}


对于上面的写法固然没有问题,但是假如我们要生成两个 person 实例对象呢?


var person1 = {
  name: 'Jack',
  age: 18,
  sayName: function () {
    console.log(this.name)
  }
}
var person2 = {
  name: 'Mike',
  age: 16,
  sayName: function () {
    console.log(this.name)
  }
}


通过上面的代码我们不难看出,这样写的代码太过冗余,重复性太高。


简单方式的改进:工厂函数


我们可以写一个函数,解决代码重复问题:


function createPerson (name, age) {
  return {
    name: name,
    age: age,
    sayName: function () {
      console.log(this.name)
    }
  }
}


然后生成实例对象:


var p1 = createPerson('Jack', 18)
var p2 = createPerson('Mike', 18)


这样封装确实爽多了,通过工厂模式我们解决了创建多个相似对象代码冗余的问题,

但却没有解决对象识别的问题(即怎样知道一个对象的类型)。


构造函数


更优雅的工厂函数:构造函数


一种更优雅的工厂函数就是下面这样,构造函数:


function Person (name, age) {
  this.name = name
  this.age = age
  this.sayName = function () {
    console.log(this.name)
  }
}
var p1 = new Person('Jack', 18)
p1.sayName() // => Jack
var p2 = new Person('Mike', 23)
p2.sayName() // => Mike


解析构造函数代码的执行


在上面的示例中,Person() 函数取代了 createPerson() 函数,但是实现效果是一样的。

这是为什么呢?


我们注意到,Person() 中的代码与 createPerson() 有以下几点不同之处:


  • 没有显示的创建对象


  • 直接将属性和方法赋给了 this 对象


  • 没有 return 语句


  • 函数名使用的是大写的 Person


而要创建 Person 实例,则必须使用 new 操作符。


以这种方式调用构造函数会经历以下 4 个步骤:


  1. 创建一个新对象


  1. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)


  1. 执行构造函数中的代码


  1. 返回新对象


下面是具体的伪代码:


function Person (name, age) {
  // 当使用 new 操作符调用 Person() 的时候,实际上这里会先创建一个对象
  // var instance = {}
  // 然后让内部的 this 指向 instance 对象
  // this = instance
  // 接下来所有针对 this 的操作实际上操作的就是 instance
  this.name = name
  this.age = age
  this.sayName = function () {
    console.log(this.name)
  }
  // 在函数的结尾处会将 this 返回,也就是 instance
  // return this
}


构造函数和实例对象的关系


使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型了。


在每一个实例对象中的__proto__中同时有一个 constructor 属性,该属性指向创建该实例的构造函数:


console.log(p1.constructor === Person) // => true
console.log(p2.constructor === Person) // => true
console.log(p1.constructor === p2.constructor) // => true


对象的 constructor 属性最初是用来标识对象类型的,但是,如果要检测对象的类型,还是使用 instanceof 操作符更可靠一些:


console.log(p1 instanceof Person) // => true


console.log(p2 instanceof Person) // => true


总结:


  • 构造函数是根据具体的事物抽象出来的抽象模板


  • 实例对象是根据抽象的构造函数模板得到的具体实例对象


  • 每一个实例对象都具有一个 constructor 属性,指向创建该实例的构造函数


注意: constructor 是实例的属性的说法不严谨,具体后面的原型会讲到


  • 可以通过实例的 constructor 属性判断实例和构造函数之间的关系


注意:这种方式不严谨,推荐使用 instanceof 操作符,后面学原型会解释为什么


构造函数的问题


使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题:


function Person (name, age) {
  this.name = name
  this.age = age
  this.type = 'human'
  this.sayHello = function () {
    console.log('hello ' + this.name)
  }
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)


在该示例中,从表面上好像没什么问题,但是实际上这样做,有一个很大的弊端。


那就是对于每一个实例对象,type 和 sayHello 都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存,如果实例对象很多,会造成极大的内存浪费。


console.log(p1.sayHello === p2.sayHello) // => false


对于这种问题我们可以把需要共享的函数定义到构造函数外部:


function sayHello = function () {
  console.log('hello ' + this.name)
}
function Person (name, age) {
  this.name = name
  this.age = age
  this.type = 'human'
  this.sayHello = sayHello
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true


这样确实可以了,但是如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题。


你肯定想到了可以把多个函数放到一个对象中用来避免全局命名空间冲突的问题:


var fns = {
  sayHello: function () {
    console.log('hello ' + this.name)
  },
  sayAge: function () {
    console.log(this.age)
  }
}
function Person (name, age) {
  this.name = name
  this.age = age
  this.type = 'human'
  this.sayHello = fns.sayHello
  this.sayAge = fns.sayAge
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
console.log(p1.sayAge === p2.sayAge) // => true


至此,我们利用自己的方式基本上解决了构造函数的内存浪费问题。


但是代码看起来还是那么的格格不入,那有没有更好的方式呢?


那就要通过原型对象的方法来建立,下面这篇博文进行介绍

相关文章
|
11天前
|
存储 JavaScript NoSQL
Node.js新作《循序渐进Node.js企业级开发实践》简介
《循序渐进Node.js企业级开发实践》由清华大学出版社出版,基于Node.js 22.3.0编写,包含26个实战案例和43个上机练习,旨在帮助读者从基础到进阶全面掌握Node.js技术,适用于初学者、进阶开发者及全栈工程师。
36 9
|
2月前
|
JavaScript 前端开发 Java
JavaScript中的面向对象编程(OOP) - 终极指南
本文介绍了 JavaScript 的面向对象编程 (OOP) 概念,包括继承、多态、封装和抽象等关键要素,并通过代码示例帮助开发者理解和应用 OOP 思维。
42 5
|
3月前
|
JavaScript 前端开发
JavaScript简介
JavaScript简介
|
3月前
|
JavaScript 前端开发 Java
js面向对象编程|24
js面向对象编程|24
|
2月前
|
Web App开发 JavaScript 前端开发
JavaWeb 22.Node.js_简介和安装
JavaWeb 22.Node.js_简介和安装
|
4月前
|
存储 移动开发 JavaScript
JavaScript简介及示例
JavaScript简介及示例
|
6月前
|
设计模式 JavaScript 前端开发
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
JavaScript的继承机制基于原型链,它定义了对象属性和方法的查找规则。每个对象都有一个原型,通过原型链,对象能访问到构造函数原型上的方法。例如`Animal.prototype`上的`speak`方法可被`Animal`实例访问。原型链的尽头是`Object.prototype`,其`[[Prototype]]`为`null`。继承方式包括原型链继承(通过`Object.create`)、构造函数继承(使用`call`或`apply`)和组合继承(结合两者)。ES6的`class`语法是语法糖,但底层仍基于原型。继承选择应根据需求,理解原型链原理对JavaScript面向对象编程至关重要
152 7
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
|
4月前
|
JavaScript 前端开发
JavaScript 简介
JavaScript 简介
46 0
|
5月前
|
前端开发 JavaScript 安全
JavaScript进阶-JavaScript库与框架简介
【7月更文挑战第11天】JavaScript库和框架加速Web开发,但也带来挑战。选择适合项目、团队技能的库或框架,如React、Angular、Vue,是关键。保持依赖更新,注意性能优化,避免过度依赖。遵循最佳实践,确保安全性,如防XSS和CSRF。学习基础,结合代码示例(如React计数器组件),提升开发效率和应用质量。
67 1
|
5月前
|
资源调度 JavaScript 前端开发
JavaScript进阶 - JavaScript库与框架简介
【7月更文挑战第5天】JavaScript库和框架构成了前端开发的核心,如jQuery简化DOM操作,Angular、React和Vue提供全面解决方案。选择时要明确需求,避免过度工程化和陡峭学习曲线。使用版本管理工具确保兼容性,持续学习以适应技术变化。示例展示了jQuery和React的简单应用。正确选择和使用这些工具,能提升开发效率并创造优秀Web应用。
55 2

热门文章

最新文章