为什么要封装
当我们要写一个系列代码的时候,比如控制音频组件的一系列方法或者其他,如果我们把代码都塞到index.js中,那么在将来的某一天你会发现index.js会变得又臭又长非常难以维护
怎么办?
作为一个分类狂魔,当然是把不同系列的js方法分到不同的js文件中啦,在js文件里封装一个对象,以后要使用这一系列的方法,找到这个对象调用对应的方法就可以了,不管三七二十一,我们先新建一个js文件,写上一个闭包
// 封装一个对象
(function(window){
function Flora() {
return new Flora.prototype.init();
}
Flora.prototype = {
constructor: Flora
init: function(){}
}
Flora.prototype.init.prototype = Flora.prototype;
window.Flora = Flora;
})(window);
这样我们的一个对象就封装好了,没有接触过面向对象的看了可能会懵逼,先了解js的面向对象再来看这段代码就好了。so easy,too happy~
JS原型
我们一步步来,首先我们先创建一个简单的类,然后new一个对象
function Cat(){}
var c = new Cat()
接着我们来回顾一下js里面原型的相关知识点
我们只要记住以下几点:
- 任何函数内部都有一个默认属性prototype,指向它的原型对象
- 任何对象内部都有一个默认属性 proto ,指向它对应的原型对象
- 函数和由函数实例化出的对象指向同一个原型对象
如何验证他们指向同一个原型对象?
我们还要记住一点:
- 任何原型对象都有一个默认属性constructor,指向它对应的函数
也就是说如果
- Cat.prototype的constructor指向Cat()
- c.__proto__的constructor指向Cat()
那么他们指向的就是同一个对象
console.log(Cat.prototype)
console.log(c.__proto__)
把他们打印一下,看一下是不是指向同一个对象
他们的constructor都指向Cat(),验证完毕~
自定义一个原型对象
因为任何函数内部都有一个默认属性prototype并且指向它的原型对象,那么我们自定义一个原型对象
function Cat() {
}
Cat.prototype = {
constructor:Cat
}
var c = new Cat()
再新增一些方法
function Cat() {
}
Cat.prototype = {
constructor:Cat,
init: function(){
this.name = "猫咪";
this.age = 1;
},
say: function(){
console.log(this.name,this,age)
}
}
var c = new Cat()
c.say()
如果我们调用c.say(),可以打印name和age吗?
控制台报错,显然是不可以的,因为在调用c.say()之前从没有调用c.init()初始化,自然没有name和age,所以每次我们都要先调用初始化方法才可以生出一个带有名字和年龄的猫咪
c.init();//必须先初始化!
c.say();
但是每次都调用init很麻烦,每一只出生的猫咪都应该有默认的姓名和年龄
如何才能不调用init就可以使用name和age?
function Cat() {
return new Cat.prototype.init();//强制初始化
}
如果没有return则返回一个对象,return了就return啥返回啥
这样总可以了吧!
我们console.log一下,发现控制台无情的报错
不能调用跟我们的原型图有关
原型关系图解
- 首先我们有一个Cat函数,它有它的proptype,它的proptype指向它对应的原型对象
- 它对应的原型对象有constructor属性,它的constructor属性指向它对应的函数
- 我们在它对应的原型对象上新增了一个init函数,init既然是一个函数就有它自己的proptype属性,它自己的proptype属性指向它自己对应的原型对象
- 它自己对应的原型对象也有constructor,它自己对应的constructor也指向init函数
- new的本质上是调用init()函数,既然就是通过init函数创建的对象,c对象的 proto 指向的就是init函数指向的原型对象
- 显而易见因为init原型对象没有say方法,所以只需要把init的原型对象改为Cat的原型对象就ok了
function Cat() {
return new Cat.prototype.init()
}
Cat.prototype = {
constructor:Cat,
init: function(){
this.name = "猫咪";
this.age = 1;
},
say: function(){
console.log(this.name,this,age)
}
}
Cat.prototype.init.prototype = Cat.prototype;
var c = new Cat();
// c.init();
c.say();
控制台很听话的打印出了我们想要的结果
梳理:
- 只要是函数,它就有它对应的原型对象,所以Cat函数和init函数都有他们各自的原型对象
- 通过某个函数创建出来的对象,这个对象的 proto 就指向这个函数的原型对象
- 你的c对象是通过init函数创建出来的,而你以后要去访问Cat的原型里面的东西,那么可以把init的原型改为Cat的原型即可。
这个思想其实就是从jq里面来的,这里粘上jq相关源码
(function( window, undefined ) {
var jQuery = function( ) {
return new jQuery.prototype.init( );
}
jQuery.prototype = {
constructor: jQuery
}
jQuery.prototype.init.prototype = jQuery.prototype;
window.jQuery = window.$ = jQuery;
})( window );
注释:window是为了将需要暴露给外界使用的,变成我们的全局变量,方便外界调用
总结
闭包的意义,让内部数据与外部隔绝,互相不造成污染