JavaScript面向对象系列连接
引言
回家过年时,家人又提到我该找个对象了,好吧,那现在就来谈谈对象吧
了解过java的童鞋们一定都知道java是个面向对象的语言,在Java的世界中,一切皆对象
面向对象的特性:封装性、继承性、多态性,这里我局部过多解释,不懂的请问度娘
我们前端开发所用的Javascript当年既然借了java的风头出世(事实上java和Javascript没半毛钱关系),现在也同样可以模仿java的面向对象编程啦~~
为什么要用面向对象的思想来编程
- 解决系统的可维护性,可扩展性,可重用性。减少结构化编程的冗余和不可重用,可维护性差的弊端
- 面向对象是模块化和组件化发展的基础,可以说一个模块要能在其他地方使用,首先它得要是一个对象。组件相当于由一个或多个对象组成的一个大的对象
- 在模块化和组件化发展的如日中天的现在,不懂面向对象的你,在面对当今三大框架肯定会有很多的困惑,学习难度大大增加
js开发中是如何产生面向对象的
- js开发中,难免会写很多的 function(){} ,随着代码体积的增大,你可能会有很多的function,这样出错后就不容易定位错误在哪儿,由此便在js中引进了单例模式
js单例模式:一个常见的用法就是在开发中把一系列的属性和方法写在一个对象中去,这样的好处一是可以避免多人开发的命名冲突,二是遇到错误可以根据对象快速定位错误地方,弊端就是单例模式一旦定义好对象,不能轻易修改内部方法,否则会出现代码杂乱可维护性变差的问题。例:
var person = { name: name, age: age, job: job, byBus: function() { ... }, cookie: function() { ... }, ... }
js单例模式并不能从根本上降低开发中大型项目的难度,没有面向对象的继承性和多态性,js工厂模式便被应用到很多项目开发中。工厂模式的核心是:在函数内创建一个对象,给对象赋予属性及方法再将对象返回,例:
// 创建工厂(类似于生产模具) function cat(name,color) { var c = new Object(); c.name = name c.color = color c.do = function() { console.log(this.name + "的颜色是" + this.color + ",它正在吃鱼!") } return c } // 生产一只白猫 var whiteCat = cat("小白", "white") // 看看这只猫现在在做什么 whiteCat.do() // 小白的颜色是white,它正在吃鱼!
有了工厂模式,就可以批量“生产”我们需要的对象了,解决了创建多个相似对象的问题。但是也只限于生产对象,无法识别对象的类型。相比这一点儿,构造函数模式就做的好得多了
// 创建函数构造器,函数名首字母要大写 function Cat(name, color) { this.name = name this.color = color this.do = function() { console.log(this.name + "的颜色是" + this.color + ",它正在吃鱼!") } } // 用函数构造器实例化一个对象 var blackCat = new Cat("大黑", "black") // 看看这只猫现在在做什么 blackCat.do() // 大黑的颜色是black,它正在吃鱼! // 判断blackCat是否是Cat的实例,即解决了工厂模式中不能 console.log(blackCat instanceof Cat) // true
构造函数相比于工厂模式有了很大程度的改进,两者差异希望读者根据我的示例自己比较。构造函数的缺点是每次实例化一个对象都会把构造器里的所有方法重新创建一次,多次创建会造成内存开销增加的问题。基于这个问题,利用js的原型和原型链来改进:
// 测试下原型构造器的使用方法: function test() { this.do = function() { console.log("do something.") } } test.prototype.done = function () { console.log('我已经做完了作业!') } console.log(test === test.prototype.constructor) // true, 说明test的原型的构造器就是test,那么test的方法是不是在test的原型构造器上也有呢,向下看 console.log(test.do === test.prototype.constructor.do) // true var a = new test() console.log(a.done === test.prototype.done) // 反过来,test也可以调用定义在test的原型链上的方法 a.do() a.done() test.prototype.do = function () { console.log('我在做作业!') } test.prototype.done = function () { console.log('我饭都吃完了,你还在做作业啊!') } var b = new test() b.do() // 原型上的方法不能改写构造器内部的方法 b.done() // 原型上的方法可以改写原型上的同名方法 // 进入正题: 将Cat构造器内的方法都放到原型上去 function Cat(name, color) { this.name = name this.color = color } Cat.prototype.do = function() { console.log(this.name + "的颜色是" + this.color + ",它正在打怪!") } // 实例化一个对象 var blueCat = new Cat("蓝猫", "blue") // 看看这只猫现在在做什么 blueCat.do() // 蓝猫的颜色是blue,它正在打怪! console.log(blueCat instanceof Cat) // true
- 原型模式实例化对象时,只需要实例化对象的属性即可,原型上的方法会被实例对象的调用,而不会在实例化时去重复创建
基于原型模式的对象的继承,只能继承构造器内部的方法,不能继承原型链上的方法:
function Cat(name, color) { this.name = name this.color = color } Cat.prototype.do = function() { console.log(this.name + "的颜色是" + this.color + ",它正在打怪!") } function RedCat(name, color) { Cat.apply(this,arguments) } var blueCat = new Cat("蓝猫", "blue") var redCat = new RedCat("虹猫", "red") console.log('redCat的颜色 ' + redCat.color) // redCat的颜色 red blueCat.do() redCat.do() // TypeError: redCat.do is not a function
- 尽管这比以前更好,但是它仍然不能满足人们对于简洁的JavaScript面向对象解决方案的渴望。这就引入了构造函数的语法糖 ==> ES6的CLASS类:让js的面向对象变得更简单
ES6的class
一个简单的,基本的class类的实例
// 类的声明 class Cat{ constructor(name, color) { this.name = name this.color = color } feature() { console.log(this.name + "的颜色为" + this.color) } like() { console.log(this.name + "喜欢吃鱼") } } // 类的实例化 let whiteCat = new Cat("小白", "white") whiteCat.feature() // 小白的颜色为white whiteCat.like() // 小白喜欢吃鱼
跟java、c++相同,js的类也具有继承(extends)、父类实例(super)、子类(subclass)、静态方法(static)、get取值和set存值等。class也具有js的一些新特性,例如,类的方法名可以是表达式、类的定义也支持表达式形式,表达式形式的定义可以写出立即执行的类
// 类的继承 class LittleCat extends Cat{ constructor(name, color, age){ super(name, color) // 子类的构造器中必须先调用super()方法,然后再使用this,否则会报错 this.age = age } fish() { console.log(this.name + "只有" + this.age + "大,但是他的梦想是去钓鱼") } static readBook() { // 静态方法不能被实例化,只能由类本身调用 console.log("动漫世界的小猫都不喜欢看书!") } set mouse(value) { this.mouse = value } get mouse() {} } let spiritCat = new LittleCat("精灵猫", "yellow", "三个月") spiritCat.like() // 精灵猫喜欢吃鱼 spiritCat.fish() // 精灵猫只有三个月大,但是他的梦想是去钓鱼 LittleCat.readBook() // 动漫世界的小猫都不喜欢看书! // spiritCat.readBook() // TypeError: spiritCat.readBook is not a function LittleCat.mouse = 10 console.log(LittleCat.mouse) // 10 // 类区别函数,不存在变量提升 let danny = new Person() // ReferenceError: Person is not defined class Person{} let serverName = "webServer" // 表达式形式定义类,此处真正的class名字是server,ser只能在server类内部使用 const server = class ser{ constructor(port) { this.port = port } // 表达式作为方法名 [serverName]() { console.log("服务器名称为: " + serverName) } start() { server.listen(this.port) console.log('server is started at port ' + this.port) } } // 表达式形式的立即执行类 const server = new class{ constructor(port) { this.port = port } // 表达式作为方法名 [serverName]() { console.log("服务器名称为: " + serverName) } start() { // server.listen(this.port) console.log('server is started at port ' + this.port) } }(8080) // 此处的server就是一个立即执行类 server.start() // server is started at port 8080
ES7新增class的静态属性(es6没有静态属性的写法),运行需要通过babel转码:
class school{ // 实例属性的写法 students = 300 // 静态属性的写法 static grade = 6 constructor() { console.log("学生总数 " + this.students) console.log("开设的年级总数 " + school.grade) } schooltime() { // ... } }