本文写作时长5小时
OO语言都有一个特点,就是都存在类的概念。通过类自定义类型,创建对象实例。和这些OO语言不同,JavaScript中是没有类的,又或者说原型对象就是JavaScript中的类。但是没有类的JavaScript是如何创建对象呢?退一步讲,在JavaScript中,对象到底是什么?
理解对象的本质
ECMA中关于对象的定义
对象就是无序属性的集合,属性的值可以是基本值,函数或者其他对象
通过JavaScript提供的Ojbect()
函数,可以创建出一个简单的对象实例
var person = new Object();
person.name = "fuckJapan";
person.age = 21;
person.sayHello = function () {
alert(this.name)
}
创建了一个person
对象,person
有三个属性,其中name
和age
是基本值,sayHello
是一个函数。
这种写法可以用字面值对象重写为:
var person = {
name:"fuckJapan",
age:21,
sayHello:function () {
alert(this.name)
}
};
这种语法看起来更加 封装
上面两种创建对象的方式,有点像手工作坊,每创建一次对象都要重新给对象添加属性,赋值,不胜其烦。怎么解决?进工厂,上生产线。
工厂模式
工厂模式抽象出创建对象的工程。既然JavaScript中没有类,所以就用函数代替,类似这样:
function createPerson(name, age, job) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayHello = function () {
alert(this.name);
}
return obj;
}
var person1 = createPerson("fuckJapan1",21,"student");
var person2 = createPerson("fuckJapan2",22,"teacher");
有了生产线,工人们再也不用亲自动手了,直接把原料放入函数,duang!一个person
,两个person
,统统new出来。工人解放了双手,提高了生产力,效率蹭蹭上升。
我是谁,我来自哪里?
虽然工厂模式解决了创建多个类似对象的问题,但是却没有解决对象识别的问题。怎么办?
构造函数模式
构造函数和普通的函数没有任何区别,只是用new
调用时会产生特殊的效果。另外构造函数有一些约定的规定,比如首字母大写。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayHello = function () {
alert(this.name);
}
}
var person1 = new Person("fuckJapan1",21,"student");
var person2 = new Person("fuckJapan2",22,"teacher");
这种模式中,函数中并没有显示创建对象,而是使用了 this
关键字给对象实例添加属性。在构造函数的结尾也没有return
。在调用时,使用了new
关键字。在用new
调用构造函数时,一共做了这么几件事:
- 分配对象内存,创建新对象
- 将这个对象和
this
绑定,this
指向这个对象 - 执行构造过程并返回对象(在构造函数末尾返回任何东西都不会起作用)
为什么这种模式解决了对象识别的问题呢?别着急,这是因为在person1
和person2
中还包含一个名为constructor
的属性,这个属性指向构造函数Person
。从此person1
,person2
不再是三无产品,是可溯源的正品,它们是由一个名为Person
的构造工厂生产的。
alert(person1.constructor == Person)//true
alert(person2.constructor == Person)//true
alert(person1.constructor == person2.constructor)//true
alert(person1 instanceof Person)//true
alert(person2 instanceof Person)//true
alert(person1 instanceof Object)//true
alert(person2 instanceof Object)//true
person1
和person2
为什么都是Object
类型,这是因为在JavaScript
中所有的对象都有一个共同的祖先Object
不走寻常路,把构造函数当做普通函数调用
Person("fuckJapan",23,"student");
alert(window.name);//fuckJapan
由于如果在浏览器中执行,则构造函数中的属性被添加到window
对象中
指定作用域
var obj = new Object();
Person.call(obj,"fuckJapanAgain",25,"student");
alert(obj.name);//fuckJapanAgain
alert(obj instanceof Person)//false
这种偷鸡摸狗的调用方法,就会导致生产出来的对象没有厂家
构造函数虽然解决了对象的识别问题,但是却没有解决对象方法的重复创建问题,person1
和person2
中的sayHello
方法是两个完全不同的方法,这是完全没有必要的
alert(person1.sayHello == person2.sayHello)//false
补救措施,将方法拿到外面,指向全局函数
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayHello = sayHello;
}
function sayHello() {
alert(this.name);
}
暂时解决了方法重复创建的问题,可是,把自己的方法拿到外面这种方式总是不妥的,全局作用域中的函数却是特定对象的方法,这太诡异了。而且方法少了还好说,多了就太乱了。
原型模式
每个函数都有一个名为prototype
的属性,指向函数的原型对象,类似OC中的类对象。原型对象中保存着创建对象实例共享的方法和属性。
function Person() {
}
Person.prototype.name = "fuckJapan";
Person.prototype.age = 29;
Person.prototype.job = "student";
Person.prototype.sons = ["son1","son2"];
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
alert(person1.name);//fuckJapan
alert(person2.name);//fuckJapan
alert(person1.sayName == person2.sayName);//true
构造函数变成了空函数,原型对象可以看做是对象的模板,所有对象生成时,默认都有这些属性和对应的值,其中引用类型是所有对象共享,非引用类型都是独立的存储。这样虽然解决了每个对象实例方法重新创建的问题,但又带来了新的问题,数据紊乱,其实不光这样一个问题,还有创建出的对象属性还需要一个个赋值,构造函数根本没什么用
person1.name = "fuckJapanAgain";
alert(person1.name);//fuckJapanAgain
alert(person2.name);//fuckJapan
基本类型的并没有受影响
person1.sons.push("son3");
alert(person1.sons);//son1,son2,son3
alert(person2.sons);//son1,son2,son3
由于引用类型是所有对象共享,所以数据乱了
说了这么多了,这个也有问题,那个也有问题,那么到底该怎么做呢?
组合使用构造函数和原型模式
在构造函数中创建每个对象需要单独使用的属性,比如sons
。在原型中创建所有对象共享的属性,比如sayHello
。
对象实例中在构造函数中创建的属性,每个都保留单独的副本,而在原型中创建的引用类型属性都共享同一份引用。同时这种模式还支持传递参数,可谓一举两得。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sons = ["son1","son2"];
}
Person.prototype = {
constructor:Person,
sayHello:function () {
alert(this.name);
}
}
var person1 = new Person("fuckJapan1",21,"student");
var person2 = new Person("fuckJapan2",26,"teacher");
person1.sons.push("son3");
alert(person1.sons);//son1,son2,son3
alert(person2.sons);//son1,son2
alert(person1.sayHello == person2.sayHello);//true
所有问题差不多都几乎是完美解决,这就是JavaScript中神奇的『类』。
除此之外,还有动态原型模式,寄生构造函数模式,稳妥构造函数模式。读者自己去研究。
说到面向对象,必须要说的就是继承,JavaScript中的「类」已经很神奇了,作为面向对象特性之一的继承则更加神奇,各种花式继承干到你哭。
欲知继承如何,且听下回分解...