JavaScript面向对象 - 构造函数

JavaScript中除了直接用 { ... } 创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。

面向对象编程的第一步,就是要生成对象。

前面说过,对象是单个实物的抽象。

通常需要一个模板,表示某一类实物的共同特征,然后对象根据这个模板生成。

典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念。

所谓“类”就是对象的模板,对象就是“类”的实例。

但是,JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。

JavaScript 语言使用构造函数(constructor)作为对象的模板。

所谓”构造函数”,就是专门用来生成实例对象的函数。

它就是对象的模板,描述实例对象的基本结构。

一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。

构造函数就是一个普通的函数,但是有自己的特征和用法。

定义一个构造函数:

function Student(name) {
    // 函数体内部使用了this关键字,代表了所要生成的对象实例。
    this.name = name;
    this.hello = function () {
        console.log('Hello, ' + this.name + '!');
    }
}

这看起来像是一个普通的函数

但是在JavaScript中,可以用 new关键字来调用这个函数,并返回一个对象:

var xiaoming = new Student('小明');

xiaoming.name;        // 小明

xiaoming.hello();    // Hello, 小明!

注意:

如果不写new,这就是一个普通函数,它返回undefined

但是,如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this

也就是说,不需要在构造函数最后写 return this;

新创建的xiaoming的原型链是:

xiaoming ---> Student.prototype ---> Object.prototype ---> null

也就是说,xiaoming 的原型指向函数 Student的原型

如果你又创建了xiaohongxiaojun,那么这些对象的原型与xiaoming是一样的

xiaoming ↘
xiaohong -→ Student.prototype ---> Object.prototype ---> null
xiaojun  ↗

new Student()创建的对象还从原型上获得了一个 constructor属性,它指向函数Student本身

xiaoming.constructor === Student.prototype.constructor;    // true

Student.prototype.constructor === Student;    // true

Object.getPrototypeOf(xiaoming) === Student.prototype;    // true

xiaoming instanceof Student;    // true

注意:

xiaoming.hello;        // function: Student.hello()
xiaohong.hello;        // function: Student.hello()
xiaoming.hello === xiaohong.hello;        // false

xiaomingxiaohong各自的hello是一个函数,但它们是两个不同的函数,虽然函数名称和代码是相同的

如果我们通过new Student()创建了很多对象,这些对象的hello函数实际上只需要共享同一个函数就可以了,这样可以节省很多内存

要让创建的对象共享一个hello函数,根据对象的属性查找原则,我们只要把hello函数移动到xiaomingxiaohong这些对象共同的原型上就可以了,也就是Student.prototype上面

function Student(name) {
    this.name = name;
}

Student.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}

new创建基于原型的JavaScript的对象就是这么简单!

忘记写new怎么办

如果一个函数被定义为用于创建对象的构造函数,但是调用时忘记了写new怎么办?

一个解决办法:使用严格模式

function Student(name) {
    'use strict';
    this.name = name;
}

Student('小明');        // TypeError: Cannot set property 'name' of undefined

strict模式下,this.name = name将报错,因为this绑定为undefinedJavaScript不允许对undefined添加属性

在非strict模式下,this.name = name不报错,因为this绑定为window

于是无意间创建了全局变量name,并且返回undefined,这个结果更糟糕

所以,调用构造函数千万不要忘记写new

为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写,这样,一些语法检查工具如jslint将可以帮你检测到漏写的new

另一个解决办法,构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象

function Student(name) {
    if (!(this instanceof Student)) {
        return new Student(name);
    }
    
    this.name = name;
    this.hello = function () {
        console.log('Hello, ' + this.name + '!');
    };
}

上面代码中的构造函数,不管加不加new命令,都会得到同样的结果

最后,我们还可以编写一个createStudent()函数,在内部封装所有的new操作

一个常用的编程模式:

function Student(props) {
    this.name = props.name || '匿名';
    this.grade = props.grade || 1;
}

Student.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}

function createStudent(props) {
    return new Student(props || {});
}

这个createStudent()函数有几个巨大的优点:

一是不需要 new来调用

二是参数非常灵活,可以传,也可以不传

var xiaoming = createStudnet({
    name: '小明'
});

xiaoming.grade;        // 1

如果创建的对象有很多属性,我们只需要传递需要的某些属性,剩下的属性可以用默认值

由于参数是一个Object,所以我们无需记忆参数的顺序

如果恰好从JSON拿到了一个对象,就可以直接创建出xiaoming

相关文章

感觉本篇文章不错,对你有收获?

¥我要小额赞助,鼓励作者写出更好的教程
80 160 120

作者:

  • 出处: https://www.mi360.cn/articles/75
  • 本文版权归作者,欢迎转载,但未经作者同意必须保留 此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

评论区

最新评论

扫码关注