JS 是面向对象语言,提到面向对象,就不得不说它的三要素之封装。
原始模式
var cat1 = {name:"guaiguai",color:"白毛"}
var cat2 = {name:"huaihuai",color:"黑色"}
存在问题:
- 多生成几个实例,代码就会很冗余,写起来也很麻烦
- cat1 与 cat2 之间看不出来有啥关联
工厂模式
function createCat(name,color) {
return {
name:name,
color:color
}
}
var cat1 = createCat("guaiguai","白毛")
var cat2 = createCat("huaihuai","黑毛")
工厂模式存在的问题
- cat1 与 cat2 之间仍然看不出有啥联系。
- 存在浪费内存,何以见得呢
function createCat(name,color) {
var obj = {}
obj.name = name;
obj.color = color;
obj.type = "猫科动物"; //不变的属性
obj.eat = function() {
alert("吃老鼠")
}
return obj
}
var cat1 = createCat("guaiguai","白毛")
var cat2 = createCat("huaihuai","黑毛")
alert(cat1.type) //猫科动物
cat1.eat()
cat2.eat()
console.info(cat1.eat == cat2.eat) // false
功能是正常的,但对于每个实例,type 和 eat 方法其实都是一模一样的内容,但却每次都重新生成,这样多占用了内存,同时影响效率。
那有没办法让 type 和 eat 方法在内存中只生成一次呢?有如下两个方式:
公共属性抽离成全局
function eat() {
alert("吃老鼠")
}
var typeName = "猫科动物"
function createCat(name,color) {
var obj = {}
obj.name = name;
obj.color = color;
obj.type = typeName; //不变的属性
obj.eat = eat
return obj
}
var cat1 = createCat("guaiguai","白毛")
var cat2 = createCat("huaihuai","黑毛")
alert(cat1.type) //猫科动物
cat1.eat()
cat2.eat()
console.info(cat1.eat == cat2.eat) // true
这样是可以实现 type 与 eat 方法的复用,但这样将变量与方法定义成全局,一来这个全局的变量或方法只是被某个对象调用,有点名不副实;二来如果要在对象中定义多个共享的属性,就得定义多个全局函数或变量
采用 prototype(原型模式)
createCat.prototype = {
type:"猫科动物",
eat:function() {
alert("吃老鼠")
}
}
function createCat(name,color) {
var obj = {}
obj.name = name;
obj.color = color;
// __proto__非标准用法
obj.__proto__ = createCat.prototype
return obj
}
var cat1 = createCat("guaiguai","白毛")
var cat2 = createCat("huaihuai","黑毛")
alert(cat1.type) //猫科动物
cat1.eat()
cat2.eat()
console.info(cat1.eat == cat2.eat) // true
console.info(cat1 instanceof createCat) // true
console.info(cat2 instanceof createCat)// true
通过在函数的原型上设置属性的方式,来达到复用 type 和 eat 方法的效果。当访问 type
时,会先在实例对象自己身上查找,如果查找不到,就会到它的原型对象查找(即 createCat.prototype
),如果还是没有,就会继续往上找,直到找到 null 为止,如果仍然没有就会报 undefined,这就是原型链
构造函数模式
Cat.prototype = {
type:"猫科动物",
eat:function() {
alert("吃老鼠")
}
}
function Cat(name,color) {
this.name = name;
this.color = color;
}
var cat1 = new Cat("guaiguai","白毛")
var cat2 = new Cat("huaihuai","黑毛")
alert(cat1.type) //猫科动物
cat1.eat()
cat2.eat()
console.info(cat1.eat == cat2.eat) // true
console.info(cat1 instanceof Cat) // true
console.info(cat2 instanceof Cat) // true
使用 Cat 作为函数名,与跟使用 createCat 作为函数名作用是一样的,只是为了区分构造函数跟普通函数,构造函数始终都应该以一个大写字母开头
如上,只要在函数前面使用 new 关键字,那么就可以少做四件事情:
- 不用创建新对象(即var obj = {}),因为 new 会帮你做(在函数中你使用 this 就可以访问到该新对象obj)
- 不用绑定原型,因为 new 会帮你做
- 不用 return 新对象,因为 new 会帮你做
即 var cat1 = new Cat(“guaiguai”,“白毛”) 主要做了如下事情:
- 创建一个新对象 cat1
- 将构造函数的作用域赋给新对象cat1(因此 this 就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象。
说明 new 关键字也只是个语法糖
自己实现个 new 操作符
function create(Con,...args){
var obj = {}
// 将 obj 实例与 Con.prototype 关联起来,达到可以访问构造函数 Con 对应的原型链上的属性和方法
Object.setPrototypeOf(obj,Con.prototype) //obj.__proto__ = Con.prototype
// 执行 Con 构造函数中的代码,将 this 绑定为当前实例对象 obj ,并传入剩余的参数
var result = Con.apply(obj,args) // var result = Con.call(obj,...args)
// 如果返回的是引用类型的对象,则返回;否则默认返回当前实例对象obj
return result instanceof Object ? result : obj
}
验证:
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.speak = function() {
alert(this.name + "speak!")
}
var p = create(Person,"张三",3)
p.speak() //张三speak!