面向对象是使用最广泛的一种编程范式,最具代表性的面向对象语言就是Java和C++,在它们的理念中,面向对象的三大特性:封装,继承,多态。类,对象,公有/私有方法/属性,各种继承就是在语法层面实现这些理念的工具。这种面向对象的理念取得了巨大的成功,以至于我们说到面向对象时,认为这是面向对象的标准。
其实以JS为代表的面向对象理念跟它们完全不同,JS才可以说是真正的面向对象语言,因为在JS中一切皆为对象。在Java/C++语言中,面向对象的最基础的两个概念是类和对象,通过类来抽象事物,通过对象来模拟具体事物。
类是静态的,类定义后,就是变成了一个"印刷模板",属性和方法都不能再变动,通过"印刷模板" 产生的对象,属性和方法也不能变动。要添加新的属性和方法,得产生新的"模板"。
在JS中没有类的概念,只有一个行为类似于"类"的对象,就是构造器函数。因为通过new表达式它也可以创建对象。但它本质上不是类,它也无法表达类的含义。
JS中的对象的属性和方法可以任意添加,删除,完全是动态化。构造器函数也是如此,因为它也是一个对象。那么从这个特性上理解就与类的概念相隔十万八千里了。
所以不要把Java/C++这类面向对象的概念往JS上套,这样很容易绕晕。要明白JS的核心一切皆对象。
通过构造器函数创建对象
构造器函数
它是JS中的"类",可以批量产生对象的函数,如下代码:
function Foo(){
...
}
var a = new Foo();
函数Foo的行为与类很类似,通过new表达式产生一个对象a。如果通过这种表面的方式去理解,可以认为它是"类"。
但是Foo函数它也是对象,只是通过new表达式,产生一个新的对象a,所以特殊的是new表达式,它在背后做了些事情,让函数Foo像类。
new表达式
函数Foo通过new
表达式来调用,会执行下面的操作:
- 创建一个全新的对象。
- 这个新对象会被执行Prototype链接,与构造器函数的原型相连。
- 这个新对象会绑定到函数调用的this。
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
函数本身就是对象,通过new表达式调用后,函数调用变成"构造函数调用"。这个函数也变成了构造器函数。
new
表达式产生的新的对象其实是与Foo对象产生了关联,并且是通过原型对象产生关联**,新对象的原型属性指向了Foo
的原型对象。
所以所谓的"类"创建"对象",其实只是改变各自的原型属性的指向,产生一个链接,从而建立关系。
(这里要注意了构造器函数产生的对象,通过 proto([[Prototype]])属性访问原型对象。
构造器函数对象通过Prototype属性访问原型)
构造器函数创建对象,如下代码:
function Foo(){
}
var a = new Foo();
//输出true
console.log(Object.getPrototypeOf(a) === Foo.prototype);
//输出true
console.log(a.__proto__ == Foo.prototype);
相互之间的链接
function Foo(name,age){
this.name = name;
this.age = age;
};
var a = new Foo("hhh",32);
//a与Foo的原型相同
console.log(Object.getPrototypeOf(a) === Foo.prototype);//输出为true
//a是否是Foo的实例
console.log(a instanceof Foo);//输出为true
//a的构造函数
console.log(a.constructor);//输出为[Function: Foo]
**通过console.log(Object.getPrototypeOf(a) === Foo.prototype)
可以知道a的原型对象与Foo的原型对象相同,这就是它们之间的联系。**后面两条语句,从表面上看就是对象与类的关系即a 是Foo的实例,a的构造函数为Foo。
instanceof
和constructor
instanceof
操作符是判断一个对象是否另一个对象的实例,constructor
属性指向对象的构造器,它们是检查对象间是否有建立关系。
instanceof操作符判断a的整条原型链中是否有指向Foo.prototype的对象,而constructor也并非是永远指向Foo。如果把这两个属性的指向一换,那建立的"类"和"对象"的关系就没了。在Jave/C++这种行为是完全被禁止。如下代码:
function Foo(name,age){
this.name = name;
this.age = age;
};
var a = new Foo("hhh",32);
//将a的原型设置为空对象(构造器函数产生的对象a要用__proto__访问原型)
a.__proto__ = {};
console.log(Object.getPrototypeOf(a) === Foo.prototype);//输出为false
//a是否是Foo的实例
console.log(a instanceof Foo);//输出为false
//a的构造函数
console.log(a.constructor);//输出为[Function: object]
加入语句a.__proto__={}
即将a的原型对象设置为空,切断了a与Foo的关联。
那么结果就不同了 instanceof
返回false
, a.constructor
指向object
,而object
并非a的构造函数。
function person(name,age) {
this.name = 'mmm';
this.age = 18;
this.msg = "123456";
}
let p = new person('cc',19);
console.log("对象p的构造对象:"+p.__proto__.constructor);
console.log("person是否是p的原型:"+person.isPrototypeOf(p));
console.log("person的原型是否p的原型:"+person.prototype.isPrototypeOf(p));