大纲:
- 工厂模式
- 构造函数
- 实例
- 原型
- 原型链
- instanceof原理
一、工厂模式
通过前一章《理解JavaScript的对象》的内容,我们对js的对象知识有了一定的了解,虽然Object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码,为了解决问题,我们常常使用工厂模式、构造函数模式或原型模式创建对象,以下简单介绍一下这几种创建对象的模式。
比如我们有一个造汽车的工厂,A用户来定制要造一辆桥车,B用户又来定了一辆,然后C也来了定一辆,客源源源不断… … 这时候我们可以把此类现象抽象成代码,我们是不是这样来写代码呢:
// 这个是给A用户的定造的车
let carA = new Object({name: 'Mercedes-Benz', color: 'red'});
// 这个是给B用户的定造的车
let carB = new Object({name: 'Mercedes-Benz', color: 'red'});
// 这个是给C用户的定造的车
let carC = new Object({name: 'BMW', color: 'black'});
// ...
// 这样如果每来一个用户就定造一辆汽车,使用new Object()创建对象,
// 这样流水线创建对象就会常生大量的重复代码;
// 我们可以使用工厂模式来创建"汽车"的方法
1.1.使用工厂模式创建汽车类:
/**
* 创建汽车
* @params name 汽车名字
* @params color 汽车颜色
* @return 返回一个汽车对象
*/
function Car(name, color) {
var car = new Object();
car.name = name;
car.color = color;
return car;
}
// 这个是给A用户的定造的车
let carA = Car("Mercedes-Benz", 'red');
// 这个是给B用户的定造的车
let carB = Car("Mercedes-Benz", 'red');
// 这个是给C用户的定造的车
let carC = Car("BMW", 'black');
工厂模式的优点只需要通过调用Car()方法接收汽车的品牌,颜色属性(信息)就能生成了非常多车的实例,解决了创建多个相似对象的问题,但工厂模式不仅产生了在函数内多次使用new创建新的对象,也没有解决对象识别的问题:
console.log(carA instanceof Car); // false
console.log(carA instanceof Object); // true
所以又产生了构造函数模式。
二、构造函数
2.1.什么是构造函数?
对象是通过函数创建的,而函数又是一种对象,构造函数也是普通函数,任何函数,只要通过new操作符来调用,那么它就可以作为构造函数。
/**
* 创建汽车
* @params name 汽车名字
* @params color 汽车颜色
* @return (隐形)返回一个汽车对象
*/
function Car(name, color) {
this.name = name;
this.color = color;
// return this; 默认返回新对象
}
// 然后我们创建汽车就可以直接使用new运算符调用构造函数就能返回汽车的实例
let carA = new Car("Mercedes-Benz", 'red');
let carB = new Car("Mercedes-Benz", 'red');
let carC = new Car("BMW", 'black');
我们发现,工厂模式和原型模式创建对象的区别:
- 没有显式地创建对象;
- 直接将属性和方法赋给了this对象;
- 没有return语句
2.2.1.理解什么是实例?
以上面的例子,只要被new关键字调用构造函数所生成的对象就叫对象实例。
let person3 = new Person('Tanya');
person3就是通过new关键字调用构造函数Person生成的对象实例。
2.2.2.new运算符调用构造函数实际上会经历以下4个步骤:
- 创建一个新对象;
- 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象。
// 我们来检测一下
console.log(carA instanceof Car); // true
console.log(carA instanceof Object); // true
我们发现通过构造函数进一步的解决了对象识别的问题,其实构造函数也有一个缺点,就是当构造函数里有公共的方法时候,那么构造函数会创建多个方法:比如汽车都有行驶功能,我们抽象成run方法:
function Car(name, color) {
this.name = name;
this.color = color;
// 如果每通过new关键字调用一次,就会重复调用创建一次公共的run方法
this.run = function () {
console.log("start running..");
}
}
let carA = new Car("Mercedes-Benz", 'red');
let carB = new Car("Mercedes-Benz", 'red');
let carC = new Car("BMW", 'black');
以上的问题思考,如果我把公共的run方法等于全局的runing方法:
function Car(name, color) {
this.name = name;
this.color = color;
// 这里的问题可以解决重复定义公共方法的问题,但问题不仅破坏了面向对象的封装性,也定义了非常多全局函数。
this.run = runing;
}
// 行驶方法
function runing() {
console.log("start running..");
}
为了解决以上的问题,衍生出了一套新解决方法,就是使用原型模式来创建对象的公共方法。
function Car(name, color) {
this.name = name;
this.color = color;
}
// 在构造函数原型上面创建公共的run方法
Car.prototype.run = function () {
console.log("start running..");
}
let carA = new Car("Mercedes-Benz", 'red');
let carB = new Car("Mercedes-Benz", 'red');
let carC = new Car("BMW", 'black');
carA.run();
要理解原型模式的工作原理,必须先理解ECMAScript中原型对象的性质:
三、原型
3.1.什么是原型?
简单来说,所有生成的函数都会一个prototype原型的属性,这个原型属性指向的是原型对象,这个原型对象的作用是共享对象的所有属性和方法,该原型对象有一个属性为constructor(构造函数),这个属性指向的是创建它的构造函数。
// 以上面的Car例子为构造函数
// 构造函数的prototype属性是一个对象,也就是原型对象
console.log(typeof Car.prototype); // object
// 构造函数的原型对象的constructor属性指向的是创建它的构造函数
console.log(Car.prototype.constructor === Car); // true
// 原型对象的作用是共享对象的所有属性和方法
Car.prototype.seat = 'cattlehide';
let car1 = new Car("Mercedes-Benz", 'red');
let car2 = new Car("Mercedes-Benz", 'black');
console.log(car1.seat); // cattlehide
console.log(car2.seat); // cattlehide
3.2.原型规则和示例
5条原型规则:
- 3.2.1.所有的引用类型(数组、对象、函数)都具有对象特性,即可自由扩展属性(null除外)
var obj = {}
obj.a = 100;
var arr = [];
arr.a = 100;
function fn (){}
fn.a = 100
- 3.2.2.所有的引用类型(数组、对象、函数)都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
- 3.2.3.所有的函数,都有一个prototype(显式原型)属性,属性值也是一个普通的对象
console.log(fn.prototype);
- 3.2.4.所有的引用类型(数组、对象、函数)__proto__属性值指向它的构造函数的prototype属性值
console.log(obj.__proto__ === Object.prototype); // true
- 3.2.5.当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找。
function Perosn(name){
this.name = name;
}
Perosn.prototype.age = 12;
var peroson = new Perosn('bob');
console.log(peroson.age); // 12
四、原型链
4.1.什么是原型链?
除了Object的prototype的原型是null
以外,所有的对象和原型都有自己的原型,对象的原型指向原型对象;在层级多的关系中,多个原型层层相连则构成了原型链。
在查找一个对象的属性时,倘若在当前对象找不到该属性,则会沿着原型链一直往上查找,知道找到为止,如果到了原型链顶端,还没找到,则返回undefined,这个查询的依据都是由原型链来进行的,这个属性在原型对象成为[[Prototype]]
,需要通过__proto__属性来进行访问。这个__proto__是一个指针,指向的是构造它的对象的prototype。
我们来了解一下原型链查询的顺序
function Perosn(name){
this.name = name;
}
let person = new Person('bob');
console.log(person.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
图例:
五、instanceof原理
- instanceof用于判断引用类型属于哪个构造函数的方法
function Person(name){
this.name = name;
}
let person = new Person('bob');
console.log(person instanceof Person); // true
原理:person.__proto__(隐式原型)一层一层往上,能否对应到Person.prototype,按照前面原型链说理解,那么也能找到Object
console.log(person instanceof Person); // true
console.log(person instanceof Object); // true
理解原型、构造函数、实例、原型(prototype),原型链关系图例:
学习完本章,建议继续学习手记《理解JavaScript的类与继承》点击跳转 希望对你学习有帮助,Thanks!