继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

理解JavaScript的构造函数、实例、原型与原型链

梁凤波
关注TA
已关注
手记 27
粉丝 4318
获赞 1744

大纲:

  • 工厂模式
  • 构造函数
  • 实例
  • 原型
  • 原型链
  • 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

图例:
img

五、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),原型链关系图例:

img

学习完本章,建议继续学习手记《理解JavaScript的类与继承》点击跳转 希望对你学习有帮助,Thanks!

打开App,阅读手记
5人推荐
发表评论
随时随地看视频慕课网APP