创建对象
工厂模式
function createPerson(name){
let o = new Object()
o.name = name
return o
}
let person1 = createPerson('lpj')
let person2 = createPerson('hjy')
缺点:不能共享、对象标识紊乱
- 单纯构造函数模式
function Person(name){
this.name = name;
this.sayName = function(){console.log(this.name)}
}
- 确保实例被标识为特定类型(通过constructor 但一般使用instanceof操作符)
-
person1 instanceof Object
==person2 instance of Person
==true
缺点:
this.sayName = function(){}
方法其实相当于this.sayName = new function(){}
也就是说方法和属性等价了,每个实例都有一个自己的方法
console.log(person1.sayName == person2.sayName)//false
原型模式
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){console.log(this.name)}
Person.prototype.job = 'student'
- 每个实例实际上都有[[prototype]]内部特性(一个指向构造器原型的指针) __ proto__实际上就用于访问本实例的[[prototype]]而不是直接访问外部
- 任何自定义的构造器函数的原型都只是单纯的函数对象,由Object()构造器创造,因此原型链到最后
Person.prototype.__proto__.__proto__ ===null
- 相关方法
-
console.log(Person.prototype.isPrototypeOf(person1)//true
直接确认原型 -
console.log(Object.getPrototypeOf(person1)== Person.prototype)
console.log(Object.getPrototypeOf(person1).job)//student
用于获取实例[[Prototype]]的值 -
setPrototypeOf
可以重写实例proto所指向的原型,但具有性能问题 -
Object.create()
也可以重写实例指向的原型,性能更好,在继承时可能会用到
-
- 遮蔽(shadow)与确认
实例如果有和原型上同名的属性,会遮蔽原型上的,使其搜索不到,可以通过delete该属性删除从而恢复原型属性的暴露
hasOwnProperty
用于确认属性在原型上还是实例上
in
用于确认可枚举是否属性在原型或在实例上
Object.keys()
返回所有可枚举属性
Object.getOwnPropertyNames()
返回全部属性包括不可枚举的
let person1 = new Person()
let person2 = new Person()
console.log(person1.hasOwnProperty("name"))//false 不在实例上
console.log("name" in person1)//true 在原型上
person1.name = 'sb'//遮蔽
console.log(person1.hasOwnProperty("name"))//true 在实例上
console.log("name" in person1)//true 在实例和原型上都有
delete person1.name//取消遮蔽
console.log(person1.hasOwnProperty("name"))//false
- 一些点
- construcor默认是不可枚举属性 要用
Object.getOwnPropertyNames()
才能返回 -
Object.keys()
和in
在内部的枚举顺序是不确定的,取决于引擎,其余的方法顺序则是确定的
- construcor默认是不可枚举属性 要用
- 原型修改与重写
-
Person.prototype.any = 'something'
可以直接添加原型的属性,并且只要添加后,任何情况下都可以访问(原型的动态性) - 记住,实例只有指向原型的指针,没有指向构造器函数的指针
-
function Person(){}
let friend = new Person
Person.prototype = {
constructor :Person,//注意 如果不写的话,这里将会从Person变成Object
name:'sb',
say(){console.log('sb')}
}
friend.say()//错误
friend指向的原型仍是最初的原型
- 尽管可以修改原生对象原型(如Array、String的),但不建议这么做,应使用es6的自定义类来继承原生类型
缺点:
- 弱化了构造函数传递初始化参数的能力例如
Person.prototype.job = 'student'
会被所有实例继承,这对函数(方法)来说还可以接受,但属性往往不需要。- 共享了属性,导致如果你
person1.job = 'teacher'
的话,person2.job
也会改变
↑这里是错误的,number和string都不会改变,这些属性是独立拷贝的,但如果是Array,显然由于引用类型的原因,你push一个新元素进去,会导致共享的全都被修改
↑注意:“独立拷贝是错的”,原型就是只有一份,这里之所以看起来person2.job不变是因为添加了person1的实例属性job,原型被遮蔽了,而person2未被遮蔽
总之,不能纯粹使用原型模式,该写构造器里就写构造器里
↑对于num和str来说,写在构造器里可以解决这个问题,但对于Array等引用来说,写在构造器里还是一样的,必须采用盗用构造器
↑草 写在构造器里还真不一样 其实很显然 因为写在构造器里就是new了新的
function Animal(){
this.foot = 4
this.colors = ['white','black']
}
let cow = new Animal
cow.colors.push("yellow")
cow.foot = 2
let cat = new Animal
console.log(cow.foot+cow.colors)//2white,black,yellow
console.log(cat.foot+cat.colors)//4white,balck
真正的缺点应该称作:原型包含引用值从而在所有实例间将会共享,这个缺点将在继承中显著体现
继承
为什么要继承?
为了让一个实例能得到多个引用类型(Person、Object等)的属性和方法,提升面向对象的能力、提高可读性、降低代码复杂度
- 基本继承
function Animal(){
this.foot = 4
}
Animal.prototype.sayMyFoot = function(){
console.log("my foot has "+this.foot)
}
function Cat(name){
this.name = name
}
Cat.prototype = new Animal()//开始继承
Cat.prototype.sayMyName = function(){
console.log("my name is "+this.name)
}
let mimi = new Cat("mimi")
mimi.sayMyName()
mimi.sayMyFoot()
//实际上mimi还继承了所有Object()原型的方法 console.log(mimi.__proto__.__proto__.__proto__.__proto__===null)//ture
注意:Cat.prototype
如果使用字面量重写的话,会导致原型链断开,因为构造器是Object了而不是Animal
Cat.prototype = {
constructor:Cat,
//constructor:Animal, constructor就是Cat啊 别乱来
sayMyName : function(){
console.log("my name is "+this.name)
}
}
Cat.prototype.__proto__ = Animal.prototype//有了这句就能修复原型链
注意:别纠结,知道原型链断开就好。这里修复后只能继承sayMyFoot,如果要继承foot的话还要call才行
原型链的问题
- 正如前面结尾所说的,原型包含引用值从而在所有实例间将会共享,而这里(通过new继承) 相当于给Cat.prototype添加了一个foot属性,这是num就没问题,如果是Array等引用值的话,就会产生问题
mimi.colors===["white","black"]
mimi.colors.push("yellow")
miao.colors===["white","black","yellow"]//受影响
mimi.foot = 2
miao.foot===4//不受影响
- 子类型在实例化时不能给父类型的构造器传参
Cat.prototype = new Animal()
你传个p参对吧
- 解决办法:盗用构造函数
利用call把Animal的this指向Cat(即call里面的第一个参数this) 这样相当于把Animal的初始化代码都偷到Cat上执行了,相当于没有两层new了,该放到构造器里的属性就放进了Cat构造器里而非跑到prototype里
function Animal(foot){
this.foot = foot
this.colors = ['white','black']
}
Animal.prototype.sayMyFoot = function(){
console.log("my foot has "+this.foot)
}
function Cat(name,foot){
this.name = name
Animal.call(this,foot)
}
//Cat.prototype = new Animal()//开始继承
Cat.prototype.sayMyName = function(){
console.log("my name is "+this.name)
} let mimi = new Cat("mimi",4)
console.log(mimi.colors)
mimi.colors.push("yellow")
let miao = new Cat("miao",4)
console.log(miao.colors)//不被影响
console.log(miao.foot)//传参成功
miao.sayMyFoot()//报错!
如cb所说:
Cat.prototype.__proto__ = Animal.prototype
等价于Objcet.setPrototypeOf(Cat.prototype, Animal.prototype)
但我觉得后者会有性能问题
还可以Cat.prototype= Object.create(Animal.prototype)//(注意不是__proto__)这个方法自己就可以重新使[[prototype]]指过去
但要补充一行Cat.prototype.constructor = Cat//因为是函数特有的默认属性
什么?这都有缺点?
是的
- 必须在构造函数中定义方法,
this.name = name Animal.call(this,foot)
因此函数不能重用- 更致命的是,子类实例也访问不到父类(Animal、Object)原型上的方法
- 组合式继承
既然访问不到方法,那就用原型链继承方法,用盗用来继承属性
这样既可以把方法定义在初始原型上实现 重用,又可以让每个实例拥有自己的属性
完整代码如下:
function Animal(foot){
this.foot = foot
this.colors = ['white','black']
}
Animal.prototype.sayMyFoot = function(){
console.log("my foot has "+this.foot)
}
//盗用构造器继承属性
function Cat(name,foot){
this.name = name
Animal.call(this,foot)
}
//第三种继承:组合继承
//在盗用的基础上,使用__proto__来进行原型链继承而不使用new
Cat.prototype= new Animal //不要用 尽管它是书里的方式
Cat.prototype.sayMyName = function(){
console.log("my name is "+this.name)
}
//你也可以选择覆写
/* Cat.prototype = {
//constructor:Animal, constructor就是Cat啊 别乱来
sayMyName : function(){
console.log("my name is "+this.name)
}
}
Cat.prototype.__proto__ = Animal.prototype*/
let mimi = new Cat("mimi",4)
console.log(mimi.colors)
mimi.colors.push("yellow")
let miao = new Cat("miao",4)
console.log(miao.colors)//不被影响
console.log(miao.foot)//传参成功
miao.sayMyFoot()//成功继承方法
注意:这里书里沙雕了,就硬要把Animal属性全部继承给Cat,从而使用Cat.prototype= new Animal,但其实没必要,反而会带来很多副作用
- 存在效率问题,在
Cat.prototype= new Animal
调用了一次Animal
在Animal.call(this,foot)
又调了一次- 由于上述问题,Cat.prototype的foot和colors属性和实例上的foot和colors是两个不同的重复
console.log(Cat.prototype.colors===mimi.colors)//false
解决方法Cat.prototype.__proto__ = Animal.prototype
- 区别是Cat.prototype搜索不到foot和colors 要能搜索到就必须new
- 缺点是失去Object.create添加属性的特性以及寄生方式DIY方法的能力
Object.setPrototypeOf(Cat.prototype, Animal.prototype)
跟上面一样,但是还会有性能问题- 标准方法:
Object.create(Animal.prototype)
然后设置constructor
即寄生式组合继承
- 原型式继承:为寄生式打基础
解决了重复调用父类构造函数的问题(直接利用了原型而不用构造器)
(直接用__ proto__重定位也是可以解决这个问题的)
试想一种函数
function object(o){
function F(){}
F.prototype = o
return new F()
}
let person = {
name : "lpj",
girlFriends: ["hjy","cst"]
}
let anotherPerson = object(person)
anotherPerson.name = "sb"//这里会添加name到实例上而不是原型上
//anotherPerson.__proto__.name = "sb"
anotherPerson.girlFriends.push("someone")//这里会直接访问原型的girlFriends
let yetAnotherPerson = object(person)
yetAnotherPerson.name = "KKP"
console.log(anotherPerson)//name is sb
console.log(yetAnotherPerson)//name is KKP
console.log(anotherPerson.__proto__)//Array has 3
console.log(yetAnotherPerson.__proto__)//Array has 3
借用 构造器简单地实现了对象之间的信息共享,不用专门去写构造器函数了
其实是对person进行浅拷贝
Objcet.create()
第二个参数还允许添加新的属性到新的实例上,注意同名会产生遮蔽
- 更进一步:寄生式继承
寄生式继承的概念是比较广的,不仅用于继承
顾名思义,用Object.create()新建一个对象,然后增强它 再返回
function createAnother(original){
let clone = Object.create(original)
clone.sayHi = function(){console.log('hi')}
return clone
}
let person = {
name : "lpj"
}
let anotherPerson = createAnother(person)
anotherPerson.sayHi()
注意:这会使得createAnother类型的函数的复用性大幅下降
-
来个大杂烩:寄生式组合继承
利用create()把Animal的原型浅复制出来,增强一个constructor属性,赋给Cat的原型就完事了
function Animal(foot){
foot = 4
this.foot = foot
this.colors = ['white','black']
}
Animal.prototype.sayMyFoot = function(){
console.log("my foot has "+this.foot)
}
//继承属性
function Cat(name,foot){
this.name = name
Animal.call(this,foot)
}
//非标准最佳方式:Cat.prototype.__proto__ = Animal.prototype 缺点是失去Object.create添加属性的特性以及寄生方式DIY方法的能力
//Cat.prototype= new Animal 原方式
function inheritPrototype(child,father){
let prototype = Object.create(father.prototype)
prototype.constructor = child
child.prototype = prototype
}
inheritPrototype(Cat, Animal)
Cat.prototype.sayMyName = function(){
console.log("my name is "+this.name)
}
到这里,实现了方法共享、属性私有(伪)、传参、且只调用了一次构造器、不破坏任何原型链、且在必要时可以添加新方法和属性