面向对象有三大特性,封装、继承和多态。
对于ES5来说,没有class的概念,并且由于js的函数级作用域(在函数内部的变量在函数外访问不到),所以我们就可以模拟 class的概念,在es5中,类其实就是保存了一个函数的变量,这个函数有自己的属性和方法。
封装:把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。
构造函数
javascript提供了一个构造函数(Constructor)模式,用来在创建对象时初始化对象。
构造函数其实就是普通的函数,只不过有以下的特点
1.首字母大写(建议构造函数首字母大写,
即使用大驼峰命名,非构造函数首字母小写)
2. 内部使用this
3.使用 new生成实例
优点:能够批量创建对象 简洁
缺点:依然为每一个新对象都生成新的方法,内存浪费
当构造函数被new的时候,系统做了如下事情:
1.创建一个object对象 new Object();
2.将对应的属性和方法绑定到obj
3.将该对象返回
function Per(name,age){
//添加属性
this.name=name;
this.age=age;
//添加方法
this.run=function(){
console.log(this.name+' is runing');
};
this.say=function(){
console.log(this.name+' is speaking')
};
}
//根据对象函数创建实例
var s=new Per('张三',22);
console.log(s.name,s.age);
s.run();
s.say();
var s1=new Per('李四',21);
console.log(s1.name,s1.age);
s1.run();
s1.say();
console.log(s.run==s1.run);//false
原型prototype
在类上通过 this的方式添加属性和对象会导致内存浪费的问题,会输出false,我们就考虑,有什么方法可以让实例化的类所使用的方法直接使用指针指向同一个方法。于是,就想到了原型的方式
Javascript规定,每一个构造函数都有一个prototype属性,
指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。 也就是说,
对于那些不变的属性和方法,我们可以直接将其添加在类的prototype 对象上。
//创建学生对象
function Student(name,age){
//添加属性
this.name=name;
this.age=age;
}
//添加方法
//将方法添加到原型中
Student.prototype.run=function(){
console.log(this.name+' is runing...');
};
Student.prototype.say=function(){
console.log(this.name+'is speaking...')
};
var s=new Student('李四',22);
var s1=new Student('张三',21);
console.log(s.name,s.age);
console.log(s1.name,s1.age);
s.run();
s1.run();
console.log(s.run==s1.run);//true
上述实例中返回结果为true,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。
在类的外部通过.语法添加
我们还可以在类的外部通过 . 语法进行添加,因为在实例化对象的时候,并不会执行到在类外部通过 . 语法添加的属性,所以实例化之后的对象是不能访问到 . 语法所添加的对象和属性的,只能通过该类访问。
三者的区别
通过构造函数、原型和. 语法三者都可以在类上添加属性和方法。但是三者是有一定的区别的。
1. 构造函数:通过this添加的属性和方法总是指向当前对象的,所以在实例化的时候,通过this添加的属性和方法
都会在内存中复制一份,这样就会造成内存的浪费。但是这样创建的好处是即使改变了某一个对象的属性或方法,
不会影响其他的对象(因为每一个对象都是复制的一份)。
2. 原型:通过原型继承的方法并不是自身的,我们要在原型链上一层一层的查找,这样创建的好处是只在内存中创建一次,
实例化的对象都会指向这个prototype 对象,但是这样做也有弊端,因为实例化的对象的原型都是指向同一内存地址,
改动其中的一个对象的属性可能会影响到其他的对象
3. **.**语法:在类的外部通过. 语法创建的属性和方法只会创建一次,但是这样创建的实例化的对象是访问不到的,只能通过类的自身访问