javascript设计模式与开发实践阅读笔记(2)—— this,闭包与高阶函数

this

this总是指向一个对象,有四种情况
1. 作为对象的方法调用。
2. 作为普通函数调用。
3. 构造器调用。
4. Function.prototype.call 或Function.prototype.apply 调用。

1. 作为对象的方法调用

当函数作为对象的方法被调用时,this 指向该对象:

var obj = {
            a: 1,
            getA: function(){
                alert ( this === obj ); // 输出:true
                alert ( this.a ); // 输出: 1
            }
        };
obj.getA();

2. 作为普通函数调用

当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。在浏览器的JavaScript 里,这个全局对象是window 对象。

window.name = ‘globalName‘;
var getName = function(){  return this.name;};
console.log( getName() ); // 输出:globalName

特别点的例子:

1 window.name = ‘globalName‘;
2 var myObject = {
3   name: ‘sven‘,
4   getName: function(){
5     return this.name;
6   }
7 };
8 var getName = myObject.getName;    //这里是把字面量给了getName
9 console.log( getName() ); // globalName

3. 构造器调用

当用new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象,见如下代码:

1 var MyClass = function(){
2     this.name = ‘sven‘;
3 };
4
5 var obj = new MyClass();
6 alert ( obj.name ); // 输出:sven

坑:

var MyClass = function(){
      this.name = ‘sven‘;
      this.app="ok";
      return { // 返回一个对象
         name: ‘anne‘,
         haha:this.app;
    }
};
var obj = new MyClass();

console.log ( obj.name ); //  anne
console.log(obj.app)    //  undefined
console.log(obj.haha)  //  ok
 

返回一个对象时,或者说接口的时候,内部的属性就无法直接访问,除非接口当中包含,这个和封装的思路是一样的。

4. Function.prototype.call 或Function.prototype.apply 调用

可以动态地改变传入函数的this:

 1 var obj1 = {
 2     name: ‘sven‘,
 3     getName: function(){
 4         return this.name;
 5     }
 6 };
 7 var obj2 = {
 8     name: ‘anne‘
 9 };
10
11 console.log( obj1.getName() ); // 输出: sven
12 console.log( obj1.getName.call( obj2 ) ); // 输出:anne

call 和apply的异同

同:第一个参数指定了函数体内this 对象的指向。
异:apply第二个参数为数组或者是类数组,call则是参数依次写出来,参数数量不固定

特别:如果我们传入的第一个参数为null,函数体内的this 会指向默认的宿主对象,在浏览器中则是window

 1 var obj1 = {
 2     name: ‘sven‘
 3 };
 4 var obj2 = {
 5     name: ‘anne‘
 6 };
 7 window.name = ‘window‘;
 8 var getName = function(){
 9     alert ( this.name );
10 };
11 getName(); // 输出: window
12 getName.call( obj1 ); // 输出: sven
13 getName.call( obj2 ); // 输出: anne

call apply bind 的区别

call和apply都是直接调用,而bind返回的是一个函数,调用的话还需要加括号。

闭包

变量的作用域

以函数为边界,外界无法直接访问到

变量的生存周期

全局变量的生存周期是永久的,除非我们主动销毁。在函数内用var关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁。

闭包实践 阶乘函数:

1 var mult = function(){
2     var a = 1;
3     for ( var i = 0, l = arguments.length; i < l; i++ ){
4         a = a * arguments[i];
5     }
6     return a;
7 };

初步的函数无法记录已经计算过的阶乘,也就是说每次输入之后都需要再次计算,这里引入缓存机制,实质就是用一个对象把键值存进去

 1 var cache = {};  //存键值的对象
 2
 3 var mult = function(){
 4     var args = Array.prototype.join.call( arguments, ‘,‘ );  //把参数变成字符串
 5
 6     if ( cache[ args ] ){    //如果有这个属性,返回这个属性值
 7         return cache[ args ];
 8     }
 9
10     var a = 1;
11     for ( var i = 0, l = arguments.length; i < l; i++ ){
12         a = a * arguments[i];
13     }
14
15     return cache[ args ] = a;    //计算的值赋给缓存的对象
16 };

这步改造实现了缓存,但是多了一个全局对象,理想状态应该所有的实现细节都放在函数内部

 1 var mult = (function(){
 2     var cache = {};   //存键值的对象
 3     return function(){
 4         var args = Array.prototype.join.call( arguments, ‘,‘ );  //把参数变成字符串
 5         if ( args in cache ){   //如果有这个属性,返回这个属性值
 6             return cache[ args ];
 7         }
 8         var a = 1;
 9         for ( var i = 0, l = arguments.length; i < l; i++ ){
10             a = a * arguments[i];
11         }
12         return cache[ args ] = a;  //计算的值赋给缓存的对象
13     }
14 })();

这步利用闭包缓存了计算的值,用匿名函数保护了变量,但是返回的函数暴露了很多没有必要的东西,应该把实现放在主体,再提炼一下

 1 var mult = (function(){
 2     var cache = {};
 3     var calculate = function(){ // 封闭calculate 函数,这里是计算实现部分
 4         var a = 1;
 5         for ( var i = 0, l = arguments.length; i < l; i++ ){
 6             a = a * arguments[i];
 7         }
 8         return a;
 9     }
10     return function(){
11         var args = Array.prototype.join.call( arguments, ‘,‘ );  //这里应该是有意暴露这个变量
12         if ( args in cache ){
13             return cache[ args ];
14         }
15         return cache[ args ] = calculate.apply( null, arguments );   //函数定义时未提供形参,这里利用apply使用函数
16     }
17 })();

闭包与内存管理

有种说法是闭包会引起内存泄漏,因为垃圾回收机制无法回收变量,但是使用闭包本身就是为了使用这些变量,或者留着以后使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的。
如果想回收这些变量,可以手动设为null。而由于循环引用导致的内存泄漏本质上也不是闭包的问题,解决方法也是设置变量为null。

高阶函数

满足下面任一条件就是高阶函数。
函数可以作为参数被传递 或者 函数可以作为返回值输出。

作为参数被传递,例如回调函数。
作为返回值输出,例如接受不同参数生成不同类型的判断器

例1:

 1 var Type = {};
 2     for ( var i = 0, type; type = [ ‘String‘, ‘Array‘, ‘Number‘ ][ i++ ]; ){
 3     (function( type ){
 4         Type[ ‘is‘ + type ] = function( obj ){
 5             return Object.prototype.toString.call( obj ) === ‘[object ‘+ type +‘]‘;
 6         }
 7     })( type )
 8 };
 9
10 Type.isArray( [] ); // 输出:true
11 Type.isString( "str" ); // 输出:true

例2:既作为参数,又作为返回值

 1 var getSingle = function ( fn ) {    //传入函数作为参数
 2     var ret;
 3     return function () {
 4         return ret || ( ret = fn.apply( this, arguments ) );  //闭包,保留ret的值,ret存在就返回ret,否则执行一次fn,并把fn的返回值赋给ret,再返回ret,即ret存储的是fn的返回值
 5     };
 6 };
 7
 8 var getScript = getSingle(function(){
 9     return document.createElement( ‘script‘ );
10 });
11
12 var script1 = getScript();
13 var script2 = getScript();
14
15 alert ( script1 === script2 );  //true  对象类型都是引用类型,所以两个指向的是同一个对象

这里利用闭包让ret变量不会被回收;fn只是个字面量,并没有执行,用apply的方式,可以执行fn并获得它的结果存到ret里面;所以getScript的执行结果是一个script节点,因为第一次执行后ret已经有值了,所以第二次还是返回那个值,无论执行几次,返回的对象都是同一个。这个就是单例模式的一个简单例子。

AOP是什么?

AOP(Aspect Oriented Programming,面向切面编程),其主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,再通过“动态织入”的方式掺入业务逻辑模块中。无关的功能包括日志统计、安全控制、异常处理等。

AOP的好处

可以保持业务逻辑模块的纯净和高内聚性,还可以很方便地复用日志统计等功能模块。

js实现AOP

 1 Function.prototype.before = function( beforefn ){
 2     var __self = this; // 保存原函数的引用
 3     return function(){ // 返回包含了原函数和新函数的"代理"函数
 4         beforefn.apply( this, arguments ); // 执行新函数,修正this,this指向window函数
 5         return __self.apply( this, arguments ); // 执行原函数
 6     }
 7 };
 8 Function.prototype.after = function( afterfn ){
 9     var __self = this;
10     return function(){
11         var ret = __self.apply( this, arguments );   //这里只是另一种写法,没有本质区别
12         afterfn.apply( this, arguments );
13         return ret;
14     }
15 };
16
17 var func = function(){  //定义一个func函数
18     console.log( 2 );
19 };
20
21 func = func.before(function(){  //重新赋值func函数
22     console.log( 1 );
23 }).after(function(){
24     console.log( 3 );
25 }).before(function(){
26     console.log( 0 );
27 });
28
29
30 func();  //执行func  0  1   2   3

这段代码实现了装饰者模式,这里在Function.prototype上添加了两个方法,因为函数都继承Function.prototype,所以所有函数都具有这两个方法;方法的内部返回了一个函数,也就是说调用完这两个方法后,返回的还是一个函数可以接着调用这两个方法。

分析这段代码需要把函数赋值和函数执行分开看。
第一步明确before和after的职责,bofore是先执行参数函数,再执行调用它的函数(本身);after是先执行调用它的函数(本身),再执行参数函数。

然后分析func这个函数。func可以拆解成这样

 1 var aa=func.before(function(){
 2     console.log( 1 );
 3 });
 4
 5 var bb=aa.after(function(){
 6     console.log( 3 );
 7 });
 8
 9 func=bb.before(function(){
10     console.log( 0 );
11 });

所以func相当于

1 func=function(){
2     console.log( 0 );
3     bb();
4 }

解析bb()

func=function(){
    console.log( 0 );
    aa();
    console.log( 3 );
}

解析aa()

func=function(){
    console.log( 0 );
    console.log( 1 );
    console.log( 2 );  //老的func
    console.log( 3 );
}

所以,执行func的结果为 0 1 2 3

时间: 2024-10-19 19:32:49

javascript设计模式与开发实践阅读笔记(2)—— this,闭包与高阶函数的相关文章

javascript设计模式与开发实践阅读笔记(3)——高阶函数的其他应用

高阶函数的其他应用 1.currying 函数柯里化,又称部分求值,一个currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来.待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值. var cost = (function(){ var args = []; return function(){ if ( arguments.length === 0 ){ var money =

javascript设计模式与开发实践阅读笔记(6)——代理模式

代理模式:是为一个对象提供一个代用品或占位符,以便控制对它的访问. 代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象.替身对象对请求做出一些处理之后,再把请求转交给本体对象.基本可以理解为粉丝(客户),经纪人(代理),偶像(对象).经纪人就相当于偶像的代理,需求直接提给经纪人,经纪人这边可以进行很多逻辑上的处理,比如可以帮助偶像过滤掉很多请求等等. 1.保护代理和虚拟代理 像上面那种,请求被代理拒绝掉就是保护代

javascript设计模式与开发实践阅读笔记(5)——策略模式

策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 我的理解就是把各种方法封装成函数,同时存在一个可以调用这些方法的公共函数.这样做的好处是可以消化掉内部的分支判断,使代码效率更高. 使用策略模式计算奖金 现在要实现这样一个东西,年终奖是根据员工的工资基数和年底绩效情况来发放的.例如,绩效为S的人年终奖有4倍工资,绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资.假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖. 思路一:创建一个函数,接收两个参数,

javascript设计模式与开发实践阅读笔记(4)——单例模式

定义 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 具体来说,就是保证有些对象有且只有一个,比如线程池.全局缓存.浏览器中的window 对象等.在js中单例模式用途很广,比如登录悬浮窗,我希望无论我点击多少次这个浮窗都只会被创建一次,这里就可以用单例模式. 1.实现单例模式 思路:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象:如果否就创建出那个对象. 1 var Singleton = function( nam

javascript设计模式与开发实践阅读笔记(7)——迭代器模式

迭代器模式:指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. 迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素. 流行语言如Java.Ruby 等都已经有了内置的迭代器实现,许多浏览器也支持JavaScript的Array.prototype.forEach. jQuery中的迭代器 1 $.each( [1, 2, 3], function( i, n ){ 2 console.log

javascript设计模式与开发实践 阅读笔记(1)

动态类型语言和静态类型语言的区别 根据数据类型的区别划分,静态语言在编译时已经确定变量的类型,动态语言在程序运行时,变量被赋予某个值之后,才具有某种类型. 静态语言在实际开发中为什么比动态语言繁琐 静态语言在编译时要进行类型检测,也就是说函数之类只能定好接收什么类型的变量.为了实现多态,可能的取值须放在一个类里面,函数接受这个类,而具体的变量则继承这个类的类型. 动态语言则不需要这么繁琐,因为他没有严格的类型检测,不过这样也会带来一些莫名其妙的问题.各有优缺点. 何为多态 接收不同参数,执行结果

javascript设计模式与开发实践阅读笔记(9)——命令模式

命令模式:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系. 说法很复杂,简单来说就是希望真正做事情的对象不要直接被调用,当我们下达一些命令之后, 希望对象已经间接的执行了.这样做的好处是可以解耦,代码可以更为灵活,还可以管理命令,甚至完成命令队列这样的操作. 实现思路 为了实现这种效果,我们需要通过一个函数,创造一个接口对象,调用接口对象的方法,就是调用对象真正的方

javascript设计模式与开发实践阅读笔记(10)—— 组合模式

组合模式:一些子对象组成一个父对象,子对象本身也可能是由一些孙对象组成. 有点类似树形结构的意思,这里举一个包含命令模式的例子 1 var list=function(){ //创建接口对象的函数 2 return { 3 arr:[], //执行列表 用来存储需要执行的对象 4 add:function(obj){ //往执行列表里添加对象 5 this.arr.push(obj); 6 }, 7 execute:function(){ //遍历执行列表,每个对象执行规定好的接口方法 8 fo

JavaScript 设计模式与开发实践读书笔记 http://www.open-open.com/lib/view/open1469154727495.html

JavaScript 设计模式与开发实践读书笔记 最近利用碎片时间在 Kindle 上面阅读<JavaScript 设计模式与开发实践读书>这本书,刚开始阅读前两章内容,和大家分享下我觉得可以在项目中用的上的一些笔记. 我的 github 项目会不定时更新,有需要的同学可以移步到我的 github 中去查看源码: https://github.com/lichenbuliren/design-mode-notes 1.currying 函数柯里化 currying 又称 部分求值 .一个 cu