文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

前端进阶(二)JS高级讲解面向对象,原型,继承,闭包,正则表达式,让你彻底爱上前端

2023-06-03 13:08

关注

JavaScript 高级

学习目标:


自己是个做了几年开发的老码农,希望本文对你有用! 这里推荐一下我的前端学习交流圈:767273102 ,里面都是学习前端的,从最基础的HTML+CSS+JS【炫酷特效,游戏,插件封装,设计模式】到移动端HTML5的项目实战的学习资料都有整理,送给每一位前端小伙伴。2019最新技术,与企业需求同步。好友都在里面学习交流,每天都会有大牛定时讲解前端技术!

面向对象介绍

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

在 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  成绩:98std2.printScore() // => 姓名:Bob  成绩 81

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

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

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

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

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


创建对象三种方法

1、调用系统的构造函数

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

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

2、字面量创建

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

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

3、工厂函数创建

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

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() // => Jackvar p2 = new Person('Mike', 23)p2.sayName() // => Mike

解析构造函数代码的执行

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

这是为什么呢?

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

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

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

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
  3. 执行构造函数中的代码
  4. 返回新对象

下面是具体的伪代码:

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) // => trueconsole.log(p2.constructor === Person) // => trueconsole.log(p1.constructor === p2.constructor) // => true

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

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

构造函数的问题

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

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) // => trueconsole.log(p1.sayAge === p2.sayAge) // => true

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


原型

内容引导:


更好的解决方案: prototype

Javascript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。

这个对象的所有属性和方法,都会被构造函数的实例继承。

这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。

function Person (name, age) {  this.name = name  this.age = age}console.log(Person.prototype)Person.prototype.type = 'human'Person.prototype.sayName = function () {  console.log(this.name)}var p1 = new Person(...)var p2 = new Person(...)console.log(p1.sayName === p2.sayName) // => true

这时所有实例的 type 属性和 sayName() 方法,

其实都是同一个内存地址,指向 prototype 对象,因此就提高了运行效率。

前端进阶(二)JS高级讲解面向对象,原型,继承,闭包,正则表达式,让你彻底爱上前端
image.png

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 资料下载
  • 历年真题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容
咦!没有更多了?去看看其它编程学习网 内容吧