概念笔记之[javascript<-2->]闭包和封装

来源:互联网 发布:ubuntu 脚本使用教程 编辑:程序博客网 时间:2024/05/19 22:57

一、闭包简介

闭包有很多用法,这种用法(在某个js文件中,把所有的代码括起来)主要目的是对代码进行封装(隔离)
如果直接使用开放式的写法,在里面定义的变量和函数可能会对全局造成污染,也有可能受到全局定义的变量和函数的污染。

假设:
在你的代码中开放式的(全局)定义了

var abc = 123; function def() {     alert('Hello!'); };

然后在页面上还引入了其它js文件,在该文件中也开放式的定义了

var abc = 'abc'; function def() {     alert('Bye!'); }

那么,使用过程中最终的结果是否还是你或者他想要的?
采用这种机制(匿名函数封装),就是为了避免这种情况。

用这种方式,可以将匿名函数外部的一些变量(对象)通过参数的方式传递到内部。
上面传递的是 jQuery 和 window 对象,这要看你具体需要用到什么。

语法格式:

首先,匿名函数闭包语法:
( function( ,w)(jQuery,window));(function(, w ) { … } ) ( jQuery, window );
两种写法,
注意括号(花括号和圆括号)的位置,以及传递的参数。

二、封装

说到封装(通常意义上的封装:封装成库/组件),首先需要认识到封装的目的是什么?
一般来说,是把经常用到的功能提取出来,独立成能够共用的代码。
这要就需要在开始之前理清楚,哪些是需要封装的,哪些是不需要封装的。(功能和实际的代码)(往往这一步才是最费神、费力、费时的地方)

这一步,一般有两个极端“小而精”和“大而全”。
对于新手(指技术或业务不熟悉,我在某些业务上也是新手),建议从“小而精”入手,原则就是
—— 只封装多个地方都需要用到的(或者都能够实现的),能不封装的尽量不封装,还有尽量降低封装的粒度(封装的每一个调用只做一件事)。

在实际进行封装操作时,从技术角度而言我们会遇到“功能性代码”和“界面操作代码”,这两者需要区别对待。
“界面操作代码”一般会封装成我们通常意义上的“界面组件”的形式。
“功能性代码”一般会封装成库的形式,这里特别注意不要掺杂进界面操作代码,或者一个功能要不掺杂进其他功能的代码,更加不要掺杂进业务代码。
所谓的“降低耦合度”,其实就是减少相互掺杂的代码。

  • 说到具体的封装实现形式是基于函数还是对象的?
    这一点不能一概而论,一般来说功能性的代码肯定是放在某个函数里面的,而将一系列的函数放到一个对象里面能够让代码更加清晰。

  • 这里提一点,js不像Java/C#/C++等语言那样在语法上有“package”或“namespace”的概念,即便是面向对象的界定在语法上来说也不是那么严格。
    由于人的语言是有限的,并且想象力也是有限的,很难避免“重名”的问题出现;
    所以js借用对象的方式来实现类似“package”/“namespace”的机制。比如:
    scooper.util.doSomething(…);
    实际上,是定义了一个 scooper 对象,然后设置一个属性 util (也是一个对象),然后下面再在 util 对象里定义具体的功能函数。
    这样做,主要为了是为了降低“重名”出现的概率。
    当然,通过函数名的前缀或者后缀来做区分也是可以的,比如说:scooper_util_doSomething(…);。

  • 说到封装具体的实现,当然又要回到上面第一点的闭包。
    前面说的那种闭包的作用是封闭一块代码(防止“重名”在运行期对功能的影响),这样封闭以后就面临怎么将内部的东西公布出去的问题。
    毕竟,我们写了一个库给别人调用总得给别人一个入口的。

将闭包中的内容公布出去有两种方式:

① 通过返回值(其实闭包就是一个函数,是函数就有返回值)。如:

var my_lib = (function(){    return {        doSomething: function(){ ... },        doSomething_2: function(){ ... }    };}());

② 通过参数对象携带出去。如:

(function(w) {    w.my_lib = {        doSomething: function(){             ...         },        doSomething_2: function(){         ...         }    };})(window);

③ 当然也可以利用作用域内的对象携带出去。如:

(function() {    window.my_lib = { // 在浏览器中 window 是个全局作用域对象        doSomething: function(){             ...         },        doSomething_2: function(){             ...         },    };})();

三、降低耦合度方面

一个库依赖另一个库或者另外几个库难以避免。
但是依赖太多,就难以独立。
如果说,某些功能调用确实是存在调用其它功能的地方,可以利用回调函数、事件通知这些方式来降低耦合,不同的复杂度可以用不同的方法。
(如果封装的功能过于复杂,其中的事件通知部分甚至可以扩展到“消息总线”的机制)
比如说封装的时候:
① 某一功能调用很简单,可以直接出结果(像 1+1 = 2 这种),就直接使用返回值的方式。
② 如果某个功能需要有一定的时间进行处理,或者调用其它的功能(定义好参数、返回值),可以用回调的方式。
③ 如果涉及到多个状态的变化,或者多次调用,可以通过事件(或者消息总线)的方式。

这里提一点,如果回调的层次过多,有可能会陷入js中著名的回调地狱(callback hell)。
这是使用的回调时候需要特别注意的地方。
当然,我们也可以用 Promise 的机制来避免这一点。(Promise是 ES6 中引入的新语法,老的浏览器可以使用 jQuery 中的 $.Deferred 来替代,也有一些其他的封装)
其实,Promise机制同字面上的意思,就是先给你个承诺(返回值对象),当有结果的时候再给你(回调)。
比如说:
doSomething()
.then(doSomething_2())
.thne(doSOmething_3());
(注意:同样 Promise 用不好,也很坑;不管是回调,还是 Promise 都只是一个方法而已,关键是你怎么用)

四、js的面向对象

js本身是函数式编程语言,面向对象并不是它的初衷。
js里面向对象的方式(类封装、继承等),也只不过是利用js函数的一些特性来进行的。

  • js 中创建对象有下面几种方式:
    ① var obj = { … };
    ② var obj = new MyObj(); // 预先定义的类
    ③ var obj = Object.create(new SomeObj()); // ES5 的新语法(老浏览器不支持)

  • js 中定义类
    js中的每次函数调用都有自己独立的上下文,这一点跟类的实例很相似。
    所以,js中是通过 function 来定义类的。
    定义好类之后,就涉及到类的属性和函数的定义。

  • 类的属性/函数,分为私有的,和公开的。

  • 私有属性/函数,只能存在于 function 内部(函数的私有变量),如:

function MyObj() {    var aaa;    function bbb() { ... }    }
  • 公开的的属性/函数有两种写法,如:
    ① 这样写:
function MyObj() {    this.prop1 = 'abc';    this.func1 = function() {     ...      };}

优点:
这样定义的属性每个对象实例都有自己的值,互不影响;
在这样定义的公开函数中可以使用私有的属性/方法;
缺点:
每次构造时都会执行该函数中的代码,在需要非常频繁的创建销毁对象时不建议用这种。

  • ② 采用 prototype 定义
    function MyObj() { … }
    MyObj.prototype.prop1 = ‘abc’;
    MyObj.prototype.func1 = function() { … };

优点:
属性跟方法在所有对象实例中都是共用一块内存的,一定程度上能够节省内存开销和提升性能;
缺点:
无法访问构造函数内部的私有属性/方法,在有一定封闭性要求的场景不建议使用。
由于内存共用,如果有变化的属性放在prototype里,所以某个对象改变这个属性后,其它都跟着变了。
如:

MyObj.prototype.arr1 = [1,2,3];var aaa = new MyObj();    aaa.push(4);var bbb = new MyObj();    console.log(bbb.arr1); // 这里输出为 [1,2,3,4] 而不是 [1,2,3],因为在aaa里改变了。// 注:对于属性的内容是 对象,数组 时存在该问题,而数字、字符串等的时候因浏览器而异

这两种方式各有优点/缺点,不能说谁好谁坏。

参考:http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html

  • js 中的类继承
    js中实现类的继承有很多种方式,但是由于js语言本身并不是适合做这事,总是达不到Java/C#这些语言中的效果;也不必去强求。
    这里不详细讲,可参考:
    https://segmentfault.com/a/1190000002440502

公司一位资深开发者对JS开发的一些解答,首先很感谢Jiang哥的这篇文章的撰写,真的受益良多,也分享给大家看看。

other:
http://www.tuicool.com/articles/JNbIvmI

0 0
原创粉丝点击