在静态语言中,一般一个类都有自己的构造函数。对于Javascript而言,它其实也有自己的‘构造函数’,但是其‘构造函数’和静态语言相比存在很大的差别。
直观来讲构造函数其实就是在构建一个实例的时候调用的函数,那在Javascript中是不是这样呢?
普通对象中的构造函数
为了证明是否会如此,我们先看一下Javascript的构造函数都在哪儿。其实它的构造函数都在其所对应的原型上,只是我们有不同的方式去拿到他。对于普通的对象来讲,如下可以查看其构造函数:
const objectA = { name: "A's name" };
objectA.constructor // ƒ Object() { [native code] }
objectA.__proto__.constructor // ƒ Object() { [native code] }
由以上例子可见,一个通过对象字面量构建的对象,其构造函数是指向原型链上层Object的。以下测试代码可以帮助更清晰的了解实例对象&Object之间构造函数的关系
objectA.constructor === objectA.__proto__.constructor // true
objectA.constructor === Object // true
objectA.consturctor === Object.prototype.constructor // true
第一行很好理解,但是第二行和第三行是怎么回事?而且可能你会问Object.constructor
又和objectA.constructor
是什么关系?
函数的构造函数
其实Object在Javascript中本质上是一个函数,一个直观的证明就是
Object instanceof Function // true
函数本身来讲也有自己的构造函数,且在声明函数之后,其原型上构造函数就默认指向了自身。
function funA() { console.log("this's function A"); }
funA.prototype.constructor === funA // true
通过new关键字来用函数构建一个实例,它的构造函数就是指向函数原型上的构造函数的
const instanceA = new funA();
instanceA.constructor === funA // true
instanceA.constructor === funA.prototype.constructor // true
这样一来其实就可以和之前的普通对象的构造函数表现一致了。区别仅仅是在实例化的时候一个使用了对象字面量,一个使用了new关键字。但是本质上来讲对象字面量实例化对象和new Object({a: "name"})
没有区别,所以这就能解释为什么objectA.constructor === Object
结果为true了
类中的构造函数
在es6提供了关键字class,可以使得我们能像静态语言一样去声明一个类,但实质上它不过是一个语法糖,内部还是由函数、原型来实现的。所以就构造函数而言它的表现其实和函数的构造是一致的。
class ClassA {
constructor() { console.log("this is classA constructor"); }
}
ClassA.prototype.constructor === ClassA // true
const instanceA = new ClassA();
instanceA.constructor === ClassA // true
instanceA.constructor === ClassA.prototype.constructor // true
是真的构造函数吗
在静态语言中,我们需要构建一个类的实例,需要使用new关键字去实例化一个对象,在使用new关键时会调用声明在类中的构造函数,那么在Javascript中是不是这样呢。我们可以拿类来做实验,这可能比较直观
class ClassA {
constructor() { console.log("this is classA constructor"); }
}
ClassA.prototype.constructor === ClassA // true
const instanceA = new ClassA(); // this is classA constructor
可以看到当我们去使用new关键直的时候,确实调用了在类中声明的构造函数中的代码。但是可能还不能这样直接断定Javascript确实是调用构造函数来构建的,看下面有趣的例子:
class ClassA {
constructor() {
console.log("this is classA constructor");
}
}
ClassA.prototype.constructor === ClassA // true
const instanceA = new ClassA(); // this is classA constructor
ClassA.prototype.constructor = function() {
console.log("this is new constructor")
}
const instanceB = new ClassA(); // this is classA constructor
首先需要说明的是,在原型上的constructor是可写的,所以我们是可以重写原型上的constructor的,如上代码所示。如果和静态语言一样,在构建实例的时候,那么就应该使用新声明的构造函数啊,但是并没有这样。这使得Javascript中的class让人难以理解,对于习惯面向对象编程的同学来说更是头疼。就光能直接重写构造函数就已经不可理喻了。但是这个其实并不是什么大问题,因为类其实在Javascript中本来就只是一个语法糖,让习惯类写法的人更加舒畅,但是其背后的本质还是在使用原型机制,所以要将静态语言中类的一套说法硬搬到Javascript是不可能说得明白的。
那既然如此constructor在Javascript中有什么用处呢?
之前看到一些比较神奇的用法是,通过它来判断一个对象是不是某个函数的实例
instanceA.__proto__.constructor === funcA.prototype.constructor
这种比较是极不可靠的,因为我们在上一个示例已经看到,你可以随意的更改一个函数或类的构造函数,但是更改之后的实例化对象仍然应该是这个函数或者类的实例,一个例子一目了然
class ClassA {
constructor() {
console.log("this is classA constructor");
}
}
const instanceA = new ClassA();
ClassA.prototype.constructor = function() {
console.log("this is new constructor")
}
const instanceB = new ClassA();
instanceA.__proto__.constructor === instanceB.__proto__.constructor // false
instanceA instanceof ClassA // true
instanceB instanceof ClassA // true
constructor的随意告诉你,它是不可靠的,所以一般我们在编程中基本上不会依赖构造函数,即使类中有使用关键字,但是其本质上是在声明函数体中的内容,并不是在声明一个你以为的‘构造函数’。原型上的constructor方法,不过是在声明方法的时候顺手加上去的,仅此而已。
小结
Javascript中的构造函数和静态类型中的构造函数是完全不同的,而且Javascript原型上的构造函数其实并没有那么可靠,它在函数声明的时候指向了函数本身,但是在函数声明之后却仍然可以被修改,这个修改并不会修改函数原来的声明。它在Javascript中是随意的也是不可靠的,所以编程中也不会特意去使用或者修改它。就让它静静的呆着就好。
ES6 提供了类的写法,但是并不能将类的一切东西生搬硬套到Javascript中,理解并接纳其本身不同于其他语言的特色才是较为重要的。
网友评论