一、设计模式
工厂模式
优点:解决了创建多个类似对象的问题
缺点:不知道创建的对象的类型
function factory(name, age) {
var obj = new Object()
obj.name = name
obj.age = age
obj.sayName = function () {
console.log(this.name)
}
return obj
}
var factory1 = factory('zhangsan', 20)
var factory2 = factory('lisi',15)
构造函数模式
Object,Array等都是原生构造函数,我们也可以自定义构造函数。
构造函数名首字母大写来区分普通函数。
优点:可以知道创建的对象的类型
缺点:构造函数内定义的方法,在每个实例上都重新创建一遍。即使将方法放在全局,构造函数内将全局函数的指针给方法,也会造成大量全局函数,没有封装性了。
function Persion(name, age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
this.sayAge = sayAge
}
function sayAge(){
console.log(this.age)
}
var persion1 = new Persion('lisi', 20)
persion1.sayName() //lisi
var persion2 = new Persion('wangwu', 19)
persion2.sayName() //wangwu
构造函数与工厂函数的不同:
1)没有显式的创建对象
2)直接将属性赋给this
3)没有return语句
创建一个实例用的是new操作符。new操作符会经历以下步骤:
1)隐式的创建一个对象
2)将this指向这个对象
3)执行构造函数中的代码
4)return这个对象
persion1和persion2是Persion的两个实例,他们都有一个construtor属性,指向Persion。
persion1.constructor == Persion //true
可以用instanceof检查实例的类型:
console.log(persion1 instanceof Persion) //true
console.log(persion1 instanceof Object) //true
构造函数直接调用时,this指向window。
原型模式
每个函数都有原型属性prototype,它是一个指针,指向一个对象,这个对象包含这个类型的所有实例共享的属性和方法。原型对象会自动获得constructor属性,这个属性也是一个指针,指向prototype所在函数。Person.prototype.constructor == Person。
当调用构造函数(Person)创建一个实例(person1)后,这个实例(person1)会有一个属性proto指向构造函数的原型(Person.prototype)。proto对脚本不可见,且这个关系只存在于实例和构造函数的原型对象之间,不存在于实例和构造函数之间。
优点:公共的方法不需要在每个实例创建一次,他们用的是同一个方法
缺点:如果原型中的属性有引用类的值,比如数组,其中一个实例修改这个值,所有实例的这个属性都将被修改。
functon Person(){
}
Person.prototype.name = 'zhangsan'
Person.prototype.age = 20
Person.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new Person()
person1.sayName() //zhangsan
var person2 = new Person()
person2.sayName() //zhangsan
组合使用构造函数模式和原型模式
这是最常用的创建自定义类型的方式,构造函数模式定义实例属性,原型模式定义方法和共享的属性。所以每个实例都会有自己的一份实例属性的副本,但又共享对方法的引用。
functon Person(name){
this.name = name
this.friend = ['zhang','li']
}
Person.prototype = {
constructor: Person,
sayName: function () {
console.log(this.name)
}
}
var person1 = new Person('wang')
var person2 = new Person('chen')
person1.friend.push('aaa')
console.log(person2.friend) // ['zhang','li']
console.log(person1.sayName === person2.sayName) //true
动态原型模式
将所有信息都封装在构造函数内。
functon Person(name){
this.name = name
this.friend = ['zhang', 'li']
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function () {
console.log(this.name)
}
}
}
二、继承
原型链
原型链是实现继承的主要方法。利用原型让一个引用类型的继承另一个引用类型的属性和方法。
实现方式:让一个构造函数的原型对象等于另一个类型的实例。
function A() {
this.name = 'A'
}
A.prototype.sayA = function () {
console.log(this.name)
}
function B() {
this.age = 10
}
B.prototype = new A()
B.prototype.sayB = function () {
console.log(this.age)
}
var c = new B()
c.sayA() // A
以上,B继承了A,通过将A的实例赋给B.prototype,实现的本质是重写原型对象,代之以一个新类型的实例,A的实例中的所有属性和方法也存在于B.prototype中了。此时,A的属性name存在于B的原型中,c.constructor = A。
当读取一个实例属性时,首先在实例中搜索该属性,如果没有搜索实例的原型,如果没有顺着原型链向上搜索。
所以以上例子搜索c.sayA()的过程是,1)搜索实例c;2)搜索B.prototype;3)搜索A.prototype,找到方法。(若A.prototype中也没有找到,则会搜索Object.prototype)
此时:
console.log(c instanceof B) //true
console.log(c instanceof A) //true
console.log(c instanceof Object) //true
原型链的问题:
- 原本在实例上的属性被继承后变成了原型属性,就出现了引用类型的属性的问题。
- 创建自类型的实例时,不能向超类型的构造函数传递参数。
借用构造函数
解决原型链的第一个问题,使用借用构造函数。
function A(name) {
this.arr = [1, 2]
this.name = name
}
function B(name) {
A.call(this, name)
}
var b1 = new B('zhangsan')
b1.arr.push(3)
console.log(b1.name) // zhangsan
console.log(b1.arr) // [1,2,3]
var b2 = new B('lisi')
console.log(b2.name) // lisi
console.log(b2.arr) // [1, 2]
优点:解决了引用类型值的问题;可以在子类型构造函数中向超类型构造函数传递参数。
缺点:无法避免将方法放在构造函数内部的问题
组合继承
又叫伪经典继承。将原型链和借用构造函数的技术组合,发挥二者之长。
function A(name) {
this.arr = [1, 2]
this.name = name
}
A.prototype.sayName = function(){
console.log(this.name)
}
function B(name, age) {
A.call(this, name)
this.age = age
}
B.prototype = new A()
B.prototype.constructor = B
B.prototype.sayAge = function(){
console.log(this.age)
}
var b1 = new B('zhangsan',20)
b1.arr.push(3)
console.log(b1.sayName()) // zhangsan
console.log(b1.arr) // [1,2,3]
var b2 = new B('lisi', 10)
console.log(b2.sayName()) // lisi
console.log(b2.arr) // [1, 2]
网友评论