JavaScript 高级
学习目标:
- 理解面向对象开发思想
- 掌握 JavaScript 面向对象开发相关模式
- 掌握在 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
- 根据 Class 创建 Instance
- 指挥 Instance 得结果
面向对象的抽象程度又比函数要高,因为一个 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)
这样封装确实爽多了,通过工厂模式我们解决了创建多个相似对象代码冗余的问题,
但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
构造函数
内容引导:
构造函数语法
分析构造函数
构造函数和实例对象的关系
- 实例的 constructor 属性
- instanceof 操作符
普通函数调用和构造函数调用的区别
构造函数的返回值
构造函数的静态成员和实例成员
- 函数也是对象
- 实例成员
- 静态成员
构造函数的问题
更优雅的工厂函数:构造函数
一种更优雅的工厂函数就是下面这样,构造函数:
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() 有以下几点不同之处:
- 没有显示的创建对象
- 直接将属性和方法赋给了 this 对象
- 没有 return 语句
- 函数名使用的是大写的 Person
而要创建 Person 实例,则必须使用 new 操作符。
以这种方式调用构造函数会经历以下 4 个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
- 执行构造函数中的代码
- 返回新对象
下面是具体的伪代码:
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
至此,我们利用自己的方式基本上解决了构造函数的内存浪费问题。
小结
构造函数语法
分析构造函数
构造函数和实例对象的关系
- 实例的 constructor 属性
- instanceof 操作符
构造函数的问题
原型
内容引导:
使用 prototype 原型对象解决构造函数的问题
分析 构造函数、prototype 原型对象、实例对象 三者之间的关系
属性成员搜索原则:原型链
实例对象读写原型对象中的成员
原型对象的简写形式
原生对象的原型
- Object
- Array
- String
- ...
原型对象的问题
构造的函数和原型对象使用建议
更好的解决方案: 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 对象,因此就提高了运行效率。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
软考中级精品资料免费领
- 历年真题答案解析
- 备考技巧名师总结
- 高频考点精准押题
- 资料下载
- 历年真题
193.9 KB下载数265
191.63 KB下载数245
143.91 KB下载数1148
183.71 KB下载数642
644.84 KB下载数2756
相关文章
发现更多好内容- 如何使用 Java 的 Arrays 类?(详细教程及示例)(java的Arrays类如何使用)
- Java 递归调用会对性能产生哪些影响?(java递归调用的性能影响 )
- Java Solr究竟支持哪些查询语法?(Java Solr支持哪些查询语法)
- Java 中的获取绝对值操作是否能应用于数组?(java获取绝对值能否应用于数组)
- Redis客户端批量操作技巧
- Java 读取文件时导致内存溢出的原因都有哪些?(java读取文件内存溢出的原因有哪些)
- 如何通过 Java Socket 编程实现双向通信?(java socket编程如何实现双向通信)
- 如何通过 Java Rsync 提升安全性?(java rsync怎样提高安全性 )
- Java FXML 国际化如何进行操作?(java fxml国际化怎么操作)
- Java Jersey 如何实现跨域请求?(详细教程及示例代码)(java jersey如何实现跨域请求)