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