面向对象的知识时JS中的一个比较重要的概念,我们今天学习一下ES5面向对象的基础内容。
一、 创建对象
工厂模式
1
2
3
4
5
6
7
8
9
10function createPerson(name){
var o = new Object();
o.name = name;
o.sayName = function(){
console.log(this.name);
};
return o;
}
var person1 = createPerson("Orange");缺点:无法辨别是一个对象。
JSON对象
1
2
3
4
5
6var person1 = {
name:"Orange",
sayName:function (){
console.log(this.name);
}
};优点:简单。
缺点:每次只能创建一个对象,代码无法复用。构造函数
1
2
3
4
5
6
7
8
9
10function Person(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("Orange");
person1.sayName(); //"Orange"优点:可以创建多个对象,可以辨别是对象。
下面我们主要讨论的也正是使用构造方法创建的对象。
二、 不使用new关键字会发生什么
不使用new的时候相当于时全局变量window调用该方法,this会绑定在全局变量上。由于没有返回值,结果是undefined。
1 | function Person(name){ |
三、 new一个对象的时候有返回值怎么办
如果返回的是一个对象,那么new出来的结果就是这个对象;如果返回的是非对象的,那么new返回的与没有return语句一样,是一个该类的实例对象。如果没有new的时候,就比如上面这种情况,那就是方法返回什么结果就是什么了。
1 | function Person1(){ |
四、 成员方法
给单个对象添加成员方法
1
2
3
4
5
6
7
8function Person(){
}
var person1 = new Person();
person1.name = "Orange";
var person2 = new Person();
console.log(person1.name);// "Orange"
console.log(person2.name);// undefined给多个对象添加成员方法
1
2
3
4
5
6
7
8
9
10
11
12function Person(name){
this.name = name;//所有的对象公用的属性
this.age = 20;//所有的对象公用的属性
}
var person1 = new Person("Orange");
var person2 = new Person("小明");
person1.age = 21;
console.log(person1.name);// "Orange"
console.log(person2.name);// "小明"
console.log(person1.age);// 21
console.log(person2.age);// 20
五、 私有变量
构造函数中var声明的变量是私有变量。
1 | function Person(){ |
六、 静态方法
添加到构造函数上的方法是静态方法,添加到构造函数上的变量是静态变量。
1 | function Person(name){ |
七、 原型
上面说过给多个对象添加成员方法,只要在构造函数中添加this.方法名 = 方法
即可,但是这样会出现一个问题就是不同对象的方法是不同的,这样就会增大内存的开销。解决这个问题的办法就是把方法放在函数的外面,然后在函数内部去引用同一个函数。这样解决了这个问题,但是却失去了封装性。解决这个问题的最终办法就是——原型。
每一构造函数写完以后,引擎会加原型的委托,就比如上面的Person类(函数)定义完了以后,引擎会加:
1 | Person.prototype = new Object(); |
从上面代码可以看出Person加了一个名叫prototype
的静态变量,这个变量就是Person的原型。由于对象是无法访问静态变量的,所以浏览器给每一个对象又加了一个__proto__
的属性也指向了这个原型。相当于引擎又执行了下面的代码:
1 | person.__proto__ = Person.prototype; |
我们这里总结一下:**对象.__proto__属性
和 构造函数.prototype属性
都指向了该类的原型**。
下面我们把这剪不断理还乱的原型画一张图吧:
由上图可知,原型对象有一个constructor
的属性指向了构造方法。也就是:
1 | Person.prototype.constructor === Person;// true |
由上面Person.prototype = new Object();
可知Person.prototype
其实是Object的对象,既然是对象那么就有__proto__
属性指向该对象的原型。也就是说:
1 | Person.prototype.__proto__ === Object.prototype;// true |
其实Object.prototype
也是一个对象那么他的原型是什么呢?我们可以打印一下发现他的原型是null
,上图也可以看出。
1 | console.log(Object.prototype.__proto__);// null |
说了这么一大堆终于知道了原型是什么了,那么还有一个更重要的东西就是原型链。
如果一个对象调用自己没有定义的属性,那么他就会从他们原型中查找,看有没有定义该属性,如果原型中没有就会去原型的原型去寻找,一直找到顶层的Object类的对象(最顶层的null不用找了,肯定没有),这条由原型连起来的链条叫做原型链。
那我们看一个东西,上面person.constructor
是什么呢?其实就是Person
函数。因为person
对象没有constructor
属性,所以去person
的原型中找也就是person.__proto__
而它里面找到了,指向的就是Person
函数。
我们也可以用原型来定义对象的属性和方法。
1 | function Person(){ |
我们来看一下这个步骤,person1
调用sayName
的时候由于person1
自己没有sayName
这个方法,那么就是去它的原型去找也就是Person.prototype
,发现有这个方法,那么就调用,而方法中this.name
,由于自己并没有name
这样一个属性,也会在原型中找,最后找到了是”Orange”。person2
找sayName
跟之前的是一样的,但是person2
有name
这个属性,所以就不需要去原型链中找了,该属性的值是”小明”。