一文搞定js的原型对象
js是一门面向对象语言,但是它并不是一门真正面向对象的语言。与完备面向对象语言java、c#的区别是没有类的概念(ES6之后新出现的类是一种语法糖),而是利用原型对象来实现面向对象继承的功能,那么原型对象到底是什么东西呢?没有类js是怎么实现的继承呢?下文就一步步的来搞定这个问题。
js对象
在java中,新建对象需要先新建类,然后通过 new 类 来得到对象,继承利用类来来实现,而js则是完全通过对象来实现的。
由于js在设计之初考虑其应用场景并没有设计太过复杂,所以没有原生 类 的概念。但是在使用中面向对象思想利于开发, 因此js之父利用其他方式实现了面对对象,也就是我们提到的原型对象
js中定义一个对象有不同的方式,最简单的就是字面量定义,如下:
var person = {
name = "alex",
age = 3,
}
console.log(person)
除了这种方式,js还支持利用 new
关键字 来定义一个对象,等等,上边不是说 js 没有类的概念吗,那 去new
谁呢 ?
实际上 java
、 c
#在执行 new class
也就是new 类来新建一个对象时其实执行的是类的构造函数,所以js之父设计了一种利用构造函数新建对象的方法,如下:
function person (name){
this.name = name
}
var p1 = new person("alex");
console.log(p.name)
new
的目标就是 构造函数
,构造函数和普通函数几乎完全相同,唯一区别就是多了 this, this指向了将要新建的实例,利用刚才的person 构造函数 其中this 指向了后来新建的 p1 实例。
上述 通过 new
构造函数 person
得到的对象 p
,如果我们想要新建一个 p2
那么可以执行:
p2 = new person("tom");
console.log(p2.name);
现在可以得到两个对象 p1 p2
,我们都知道 对象是由属性和方法组成的,现在构造函数中只定义 name 属性,没有方法,如果我们想定义一个 sayName
方法,我们可以修改 构造函数:
function person (name){
this.name = name;
this.sayName = function(){
console.log("my name is ",this.name)
}
}
var p1 = new person("alex");
var p2 = new person("alex");
p1.sayName();
p1.sayName();
在构造函数上新增方法能够实现对象的公共方法,但是却会造成内存浪费,因为生成的每个对象的 sayName方法是相同的,每个对象都拥有了一份相同代码。
能不能把相同公共的方法从每个对象中提取出来呢?答案是可以。
原型对象
在每个js对象中都有一个 prototype
属性 (在谷歌浏览器中表现为_proto_),该属性指向一个对象,也就是我们常说的原型对象
。
上图中的 Object 就是 原型对像,每个实例对象都指向同一个原型对象。
那么我们就可以将一些公共方法放在原型对象上了,只要在实例对象上定义一次,其他实例对象也可以使用该方法,如下
p1.__proto__.sayHello = function(){
console.log("hello");
}
p1.sayHello();
p2.sayHello();
说明 实例对象 p1 和 p2 只存放了 对 sayHello的 指针,并没有复制 sayHello方法,解决上述内存占用过多的问题。(其实 存放指针这一说法并不准确,应该是向上查找,这里暂时这样理解)
在上图我们也可以直接看到 sayHello
方法确实加到了实例对像 p1 的原型对象上。
从上图我们还可以看出,实例对象p1的原型对象中处理自定义的 sayHello
方法还有一个 constructor
属性,而且可以看到 它指向了我们之前定义的person
构造函数。你可能会疑惑,这绕来绕去的是为了什么呢?其实就是为了下面将要提到的继承。
为了更好的理解,这里用一张图表示 实例对象
、 原型对象
、 构造函数
之间的关系。
通过这张图,能很清晰看出三者关系:构造函数 new 出来实例对象,实例对象的prototype属性指向一个原型对象,原型对象的constructor属性又指向构造函数,三者形成一个闭环
面向对象语言的三个特点是:封装、继承和多态 ,那么js如何实现呢?
继承
js的继承并非像 java 语法上利用 extends 关键字就能简单的实现。
其实在上文中我们增加 实例对象p1 的原型对象的 sayHello 方法后 实例对象p2 就能调用该方法,这就变相实现了一种继承。
//todo 待续
封装
自执行函数