1. 背景介绍
面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可
以创建任意多个具有相同属性和方法的对象。但是ECMAScript 中没有类的概念,因
此它的对象也与基于类的语言中的对象有所不同。
ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,
这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射
到一个值。正因为这样(以及其他将要讨论的原因),我们可以把 ECMAScript 的对象想象成散列表:无
非就是一组名值对,其中值可以是数据或函数。
面向对象编程的特征:继承/封装/多态/多线程/低耦合/组件编程
本期小课堂主要讨论如何创建对象(封装);
2. 知识剖析
1. Object实例
创建自定义对象的最简单方式就是创建一个 Object 的实例,然后再为它添加属性和方法,如下所示。
//创建一个 Object 的实例var person = new Object();person.name = "严泽浩";person.age = 23;person.job = "菜鸡web";person.sayName = function(){return this.name;};console.log(person.sayName());//严泽浩
上面的例子创建了一个名为 person 的对象,并为它添加了三个属性(name、age 和 job)和一个
方法(sayName())。其中,sayName()方法用于显示 this.name(将被解析为 person.name)的值。
前面的例子用对象字面量语法可以写成这样:
//对象字面量语法var person = {name: "火箭强",age: 22,job: "流浪歌手",sayName1: function(){return this.job;}};console.log(person.sayName1());//流浪歌手
虽然 Object 构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同
一个接口创建很多对象,会产生大量的重复代码。
2. 工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。考虑到在 ECMAScript 中无法创建类,开发人员就发明了一种函数,用函数来封装一些以特定接口创建对象的细节。
//工厂模式function createPerson(name, age, job){var o = new Object();o.name = name;o.age = age;o.job = job;return o;}var 火箭强 = createPerson("火箭强", 22, "流浪歌手");console.log(火箭强);var 严泽浩 = createPerson("严泽浩", 23, "菜鸡web");console.log(严泽浩);
函数 createPerson()能够根据接受的参数来构建一个包含所有必要信息的 Person 对象。可以无
数次地调用这个函数,而每次它都会返回一个包含三个属性的对象。工厂模式虽然解决了创建
多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。随着 JavaScript
的发展,又一个新模式出现了。
3. 构造函数模式
ECMAScript 中的构造函数可用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
//构造函数模式function Person(name, age, job){this.name = name;this.age = age;this.job = job;}var 火箭强 = new Person("火箭强", 22, "流浪歌手");var 严泽浩 = new Person("严泽浩", 23, "菜鸡web");
与工厂模式的不同:
- 没有显式地创建对象;
- 直接将属性和方法赋给了 this 对象;
- 没有 return 语句。
此外,还应该注意到函数名 Person 使用的是大写字母 P。按照惯例,构造函数始终都应该以一个
大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴自其他 OO 语言,主要是为了
区别于 ECMAScript 中的其他函数;因为构造函数本身也是函数,只不过可以用来创建对象而已。
4. 原型模式
我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,
而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那
么 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以
让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是
可以将这些信息直接添加到原型对象中
//原型模式function Person(){};Person.prototype.name = "火箭强";Person.prototype.age = 22;Person.prototype.job = "流浪歌手";Person.prototype.sayName = function(){return this.name};var person1 = new Person();console.log('person1.sayName();', person1.sayName()); //"流浪歌手"var person2 = new Person();console.log('person2.sayName();', person2.sayName()); //"流浪歌手"console.log(person1.sayName == person2.sayName);//true
在此,我们将 sayName()方法和所有属性直接添加到了 Person 的 prototype 属性中,构造函数
变成了空函数。即使如此,也仍然可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属
性和方法。但与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。换句话说,
person1 和 person2 访问的都是同一组属性和同一个 sayName()函数
3. 常见问题
js中的new()到底做了些什么??
4. 解决方案
要创建一个对象的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4个步骤:
- 新建一个对象o
-
o. __proto__ = Object.prototype将新创建的对象的__proto__属性指向构造函数的prototype - 将this指向新创建的对象
- 返回新对象
5. 编码实战
6. 扩展思考
面向对象和面向过程的区别?
面向对象和面向过程是两种不同的编程思想,我们经常会听到两者的比较,刚开始编程的时候,大部分应该都是使用的面向过程的编程,但是随着我们的成长,还是面向对象的编程思想比较好一点~
其实面向对象和面向过程并不是完全相对的,也并不是完全独立的。面向对象和面向过程的主要区别是面向过程主要是以动词为主,解决问题的方式是按照顺序一步一步调用不同的函数。
而面向对象主要是以名词为主,将问题抽象出具体的对象,而这个对象有自己的属性和方法,在解决问题的时候是将不同的对象组合在一起使用。
所以说面向对象的好处就是可扩展性更强一些,解决了代码重用性的问题。
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
- 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
如何把大象装进冰箱?
- 面对过程方法:
- 开门(冰箱);
- 装进(冰箱,大象);
- 关门(冰箱)。
- 面对对象方法:
- 冰箱.开门()
- 冰箱.装进(大象)
- 冰箱.关门()
7. 参考文献
参考书籍:JavaScript高级程序设计(第三版)
8. 更多讨论
问题一:构造函数模式存在的缺陷?
构造函数方法很好用,但是存在一个浪费内存的问题。每一次生成一个实例,都必须为重复的内容,多占用一些内存。这样既不环保,也缺乏效率。
问题二:原型模式相比构造函数模式的优势有哪些?
通过原型继承的方法并不是自身的,我们要在原型链上一层一层的查找,这样创建的好处是只在内存中创建一次,实例化的对象都会指向这个prototype 对象
问题三:原型模式存在的弊端有哪些?
实例化的对象的原型都是指向同一内存地址,改动其中的一个对象的属性可能会影响到其他的对象