面向对象的语言都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象,ES6之前并没有类的概念,在ES6中引入类class.
ES5 面向对象
创建对象(四种模式简介,此外还有动态原型模式、寄生构造函数模式、稳妥构造函数模式等)
一、工厂模式
function createPerson (Name,Age,Job) {
var man= new Object();
man.name= Name;
man.age= Age;
man.job= Job;
man.sayName= function () {
alert(this.name)
}
return man;
}
var personOne= createPerson ("Erric",26,"Engineer");
var personTwo= createPerson ("Lori",26,"teacher");
优点:解决了多个相似对象的创建问题
缺点: ① 对象识别问题无法解决(即怎么知道一个对象的类型)
二、构造函数模式
function Person (Name,Age,Job) {
this.name = Name;
this.age = Age;
this.job= Job;
this.sayName= function () {
alert(this.name)
}
}
var personOne= new Person("Erric",26,"Engineer");
var personTwo= new Person("Lori",26,"teacher");
注一: 若不使用new操作符直接调用函数,那么其属性和方法都会被添加到window对象里面(因为在全局作用域调用一个方法时,this总是指向window对象)
如: Person("Erric",26,"Enginee")
window.sayName() // 弹出 "Erric"
window.name // "Erric"
window.age // 26
注二: new 操作符实际上进行了以下操作
① 创建一个新的对象
② 将构造函数的作用域赋给新对象(this指向了这个新的对象)
③ 执行构造函数中的代码(为这个新对象添加属性)
④ 返回这个新的对象
优点:① 不用显式的创建对象
② 将属性和方法赋给了this对象
③ 没有return语句
缺点:① 每个方法都要在每个实例上重新创建一遍(personOne和personTwo中的sayName方法不是同一个方法,每个函数都是一个对象,故每 定义了一个函数就实例化了一个对象)。
此问题也可以通过将方法单独抽出来解决(但是方法一多,都移到全局的话封装性就无从谈起),如下:
function Person (Name,Age,Job) {
this.name = Name;
this.age = Age;
this.job= Job;
this.sayName= sayName
}
function sayName() {
alert(this.name)
}
var personOne= new Person("Erric",26,"Engineer");
var personTwo= new Person("Lori",26,"teacher");
② 若是将公共的sayName方法移到全局,那么又没有封装性可言了。
三、原型模式
function Person () {
}
Person.prototype.name= "Erric"
Person.prototype.age= "28"
Person.prototype.job= "Job"
Person.prototype.sayName= function () {
alert(this.sayName)
}
优点:① 解决了函数共用的问题,不用每个实例都创建一遍方法。
缺点:① 不能传参
② 如果实例中修改了原型中的属性(引用类型)或方法,那么这个属性或方法会被彻底的修改,而影响到其他实例。
四、构造函数+原型组合模式
function Person (Name,Age,Job) {
this.name= Name
this.age= Age
this.job= Job
}
Person.prototype.sayName= function () {
alert(this.name)
}
// 上面往原型上添加属性和方法的也可如下写,但是此时原型的constructor不指向Person构造函数,而是指向Object,因为Person.prototype就像一个新的对象实例,它的__proto__指向Object原型。
// Person.prototype= {
constructor: Person, // 重新再实例中定义constructor的指向,覆盖Object原型中的constructor指向
sayName: function () {
alert(this.name)
}
}
var personOne= new Person("Erric",26,"Engineer");
var personTwo= new Person("Lori",26,"teacher");
原型对象的理解(重要)
1.首先得明白以下三点:
① 每个函数(含构造函数)都有一个prototype属性,指向Person原型
② 每个实例都有一个__proto__属性,也指向Person原型
③ 每个原型都有一个constructor属性,指向其对应的构造函数
构造函数、实例、原型三者关系如下图:
2.万物皆对象,说明原型链的最开始点都是Object,所以任何一个引用类型的 instanceof Object都会返回true。
类的继承(两种方式)
一、原型链继承
对于什么是原型链?
每个构造函数都有一个原型对象,原型对象的constructor指向这个构造函数本身,而实例的__proto__属性又指向原型对象。这个假设一个实例的__proto__内部指针指向其原型,而它的原型又是另一个类型的实例,那么它的原型又将指向另一个原型,另一个原型也包含一个指向它的构造函数的指针,假设另一个原型又是另一个类型的实例,这样层层递进,就构成了实例与原型的链条,这就是原型链的基本概念。
实现原型链的继承方式基本如下:
function Father () {
this.appearance = "beautiful"
}
Father.prototype.sayHappy = function () {
alert("快乐")
}
function Child () {
this.name= "Jhon"
}
Child.prototype= new Father() // 继承了父类的方法和属性
Child.prototype.addArr= [1,2,3,4,5]
var child= new Child()
child.sayHappy() // 弹出“快乐”
child.appearance // "beautiful"
child.addArr // [1,2,3,4,5]
原型链继承的缺点:① 不能传参 ② 若原型上的方法时引用类型的话,不小心被修改了的话会影响其他实例。
二、借助构造函数继承(利用calll和apply改变this指针)
基本思路:在子类型构造函数的内部调用超类型的构造函数。
function Father (Hobby){
this.hobby= Hobby
}
Father.prototype.sayHappy = function () {
alert("快乐")
}
function Child () {
this.name= "Jhon"
Father.call(this,"Play Games") // 或者Father.apply(this,["Play Games"]),继承了Father的属性和方法
}
var child = new Child()
child.sayHappy // 没有反应,原型上的方法和属性不会继承
child.hobby // "Play Games"
借助构造函数继承的缺点:① 方法都在构造函数中定义,函数的复用无从谈起 ② 超类中的方法对子类不可见。
三、组合继承(也叫经典继承,将原型链和借助构造函数继承相结合)
思路:1.原型链实现对原型属性和方法的继承;
2.构造函数实现对实例属性的继承,且调用基类的构造函数;
function Father(Hobby) {
this.hobby= Hobby;
this.exGF = ['cuihua', 'erya']
}
Father.prototype.sayHappy = function () {
alert("快乐")
}
function Child () {
this.name= "Jhon"
Father.call(this,"Play Games") // 或者Father.apply(this,["Play Games"]),继承了Father的属性和方法
}
Child.prototype= new Father()
Child.prototype.sayName= function () {
alert(this.name);
}
var liHua= new Child()
liHua.sayHappy()
liHua.sayName()
检测对象属性的两种方法:
object.hasOwnProperty(属性名),这个方法检测的是对象实例的属性(若是返回true),不能检测原型上的属性。
in操作符,检测对象所有的属性,包含原型和实例上的额,有的话就返回true.
判断一个原型是否在某个实例的原型链上:
Person.prototype.isPropotypeOf(personOne) // true
Object.prototype.isPropotypeOf(personOne) // true
判断一个构造函数是否在实例的原型链中出现过:
personOne instanceof Person // true
personOne instanceof Object // true
ES6 面向对象
ES6中引入了Class(类)这个概念,通过关键字class可以创建一个类。类的数据类型就是函数,类的所有方法都定义在prototype属性上。
class Person () {
constructor (x,y) {
this.name= x
this.age= y
}
sayName () {
alert("快乐")
}
}
var liHua= new Person("张俊泽",26)
注: 可以理解为constuctor中的属性和方法为ES5中的构造函数部分,和constructor同级的是ES5中原型上的方法和属性。
ES6的继承通过extends关键字实现
class Father(){}
class Child extends Father {
constructor(x,y,color){
super(x,y)
this.color= color
}
toString() {
retunr "世界和平!"
}
}
上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
类的prototype和__proto__属性
Class作为构造函数的语法唐,同时有prototype和__proto__属性,因此存在两条继承链:
① 子类的__proto__,表示构造函数的继承,总是指向父类
② 子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class Father {
}
class Child extends Father{
constructor () {
super()
}
}
var childOne= new Child()
Child.__proto__ == Father // true
childOne.__proto__ == Child.prototype // true
Child.prototype.__proto__ == Fahter.prototype // true