《JavaScript模式》--第四章:函数

/*
	函数
*/
/*
	JS中的函数有两个主要特点
		函数时第一类对象
		可以提供作用域

	函数就是对象
		可以在运行时动态创建,还可以在程序执行过程中创建
		函数可以分配给变量,可以将它们的引用复制到其他变量,可以被扩展,绝大多数情况下,函数还可以被删除
		可以作为参数传递给其他函数,并且还可以由其它函数返回
		可以有自己的属性和方法
*/
/*----------------------------START函数定义的三种方式--------------------------------------*/
/*
	消除术语歧义
		命名函数表达式
		未命名函数表达式、函数表达式、匿名函数
		函数声明
*/
/*命名函数表达式*/
var funName_1 = function funName_1(param1, param2) {
	console.log(param1, param2);
};

/*未命名函数表达式,也简称函数表达式,或叫做匿名函数*/
var funName_2 = function(param1, param2) {
	console.log(param1, param2);
};

var funName_3 = function specialName(param1, param2) {
	console.log(param1, param2);
};

/*
	上述两种函数表达式唯一区别在于该函数对象的name属性是否为一个空字符串
		name属性是JS语言的一个扩展,并不属于ECMA标准的一部分
*/
console.log(funName_1.name);
console.log(funName_2.name);
try {
	specialName("param1", "param2");
} catch (e) {
	console.log(e.message);
}
// 输出:
// funName_1
// funName_2
// 错误:Uncaught ReferenceError: specialName is not defined 

/*函数声明*/

function funName_4(param1, param2) {
	console.log(param1, param2);
}
console.log(funName_4.name);
// 输出:
// funName_4

/*
	函数字面量这个术语可能表示一个函数表达式或命名函数表达式,比较模糊,所以应该避免使用
*/

/*
	声明Vs表达式:名称和变量声明提升
		函数声明只能出现在“程序代码”中,也就是说只能在其他函数体内部或全局空间中,他们的定义不能分配给变量或是属性,也不能以参数形式出现在函数调用中

	函数的name属性
		只有匿名函数的name属性为空字符串或是未定义--根据具体浏览器实现,命名函数表达式和函数申明都有name属性

	函数的提升
		命名函数表达式和函数声明的区别在于函数提升
		提升的不仅仅是函数声明,函数的定义也被提升
*/
/*命名函数表达式及匿名函数不会被提升*/

function fun1() {
	console.log("In fun1");
}

function fun2() {
	console.log("In fun2");
}

(function hoisting() {
	console.log(fun1);
	console.log(fun2);

	function fun1() {
		console.log("In fun1 of hoisting");
	}

	var fun2 = function() {
		console.log("In fun2 of hoisting");
	};
})();
// 输出:
//  function fun1() {
// 		console.log("In fun1 of hoisting");
// 	} 
//  undefined
// 分析:
// hoisting函数中的fun1被提升,所以console.log(fun1)输出了hoisting中fun1函数的定义
// 仅仅变量fun2被提升,而匿名函数未被提升,所以输出的是undefined
/*----------------------------END函数定义的三种方式--------------------------------------*/

/*----------------------------START回调模式--------------------------------------*/
/*
	所谓回调,就是将一个函数A传递给另外一个函数B,函数根据自己的情况在适当的时候调用函数A
		回调模式中回调函数的this对象需要注意是指向全局对象的,在浏览器中也就是window对象
		当回调函数是一个对象的方法时,希望回调函数中的this指向这个对象,则需要将这个对象一并传入
*/

var callbackExample = function(callback, obj) {
	if (typeof callback === 'function') {
		callback.call(obj);
	}
};

function fun1() {
	console.log(this);
}

var fun2 = function() {
	console.log(this);
};

var someObj = {
	fun3: function() {
		console.log(this);
	}
};

callbackExample(fun1);
callbackExample(fun2);
callbackExample(someObj.fun3, someObj);
// 输出:
// Window {top: Window, window: Window, location: Location, external: Object, chrome: Object…}
// Window {top: Window, window: Window, location: Location, external: Object, chrome: Object…}
// Object {fun3: function}
/*----------------------------END回调模式--------------------------------------*/

/*----------------------------START自定义函数模式--------------------------------------*/
/*
	自定义函数模式
		函数在函数体内部改变自己的函数定义的模式

		也可以叫 惰性函数定义

		缺点:
			它重定义自身时已经添加到原始函数的任何属性都会丢失
			如果该函数使用了不同的名称,那么重定义部分将永远不会发生,并且将会执行原始函数体
*/
var someFun = function() {
	console.log("Before changing");
	someFun = function() {
		console.log("After changing");
	};
};
someFun();
someFun();
// 输出:
// Before changing 
// After changing

/*----------------------------END自定义函数模式--------------------------------------*/

/*----------------------------START即时函数模式--------------------------------------*/
/*
	即时函数
		在定义函数后立即执行该函数的语法
		由以下几部分组成:
			可以使用函数表达式定义一个函数(不能用函数声明)
			在末尾添加一组括号,这将导致函数立即执行
			将整个函数包装在括号中(只有不讲该函数分配给变量才需要这么做)
		可以为初始化代码提供一个作用域的sandbox
		将所有代码包装到它的局部作用域中,可以防止全局作用域的污染

*/
/*第一种语法*/
(function() {
	console.log("Running!");
}());

/*第二种语法*/
(function() {
	console.log("Running!");
})();
// 输出:
// Running!
// Running!

/*
	即时函数也可以传递参数
		全局对象是以参数方式传递给即时函数的,以便在不适用window:指定全局作用域限定的情况下可以在函数内部访问该对象
*/
(function(global) {
	console.log(typeof global.setInterval);
}(window));
// 输出:
// function

/*
	即时函数的返回值
		即时函数也可以返回值,可以将这些返回值分配各变量
*/

var result = (function(firstName, lastName) {
	return "My name is : " + firstName + " " + lastName;
}("Bill", "Gates"));
console.log(result);
// 输出:
// My name is : Bill Gates 

/*
	即时函数的优点
		包装想要执行的任务,而不用担心会污染全局对象
*/
/*----------------------------END即时函数模式--------------------------------------*/
/*----------------------------START即时对象的初始化模式--------------------------------------*/
/*
	即时对象的初始化模式
		使用带有init()方法的对象,该方法在创建对象后将会立即执行,init()函数负责所有的初始化任务
*/
/*实例*/
var initOptions = ({
	firstName: "",
	lastName: "",
	getWholeName: function() {
		return "My name is : " + firstName + " " + lastName;
	},
	init: function() {
		firstName = "Bill";
		lastName = "Gates";
		console.log(this.getWholeName());
		//...
		return this;
	}
}).init();
console.log(initOptions);
// 输出:
// My name is : Bill Gates 
// Object {firstName: "", lastName: "", getWholeName: function, init: function}

/*
	这种模式主要适用于一次性的任务,如果在init中没有return this语句,将没有对该对象的访问
	保护了全局命名空间
*/

/*----------------------------START初始化时分支模式--------------------------------------*/
/*
	初始化分支模式
		也称为加载时分支,是一种优化模式,当知道某个条件在整个程序生命周期内都不会发生改变,仅对该条件测试一次是很有意义的
*/
/*优化前浏览器嗅探实例*/
var utils = {
	addListener: function(el, type, fn) {
		if (typeof window.addEventListener === 'function') {
			el.addEventListener(type, fn, false);
		} else if (typeof document.attachEvent === 'function') {
			el.attachEvent('on' + type, fn);
		} else {
			el['on' + type] = fn;
		}
	},
	removeListener: function(el, type, fn) {
		if (typeof window.removeEventListener === 'function') {
			el.removeEventListener(type, fn, false);
		} else if (typeof document.detachEvent === 'function') {
			el.detachEvent('on' + type, fn);
		} else {
			el['on' + type] = null;
		}
	}
};
utils.addListener(document.body, "click", function() {
	console.log("Body has been loaded");
});
// 输出:
// Body has been loaded 

/*优化前,每次调动addListener和removeListener,都会进行一次浏览器嗅探的计算,效率较低*/

/*优化后浏览器嗅探实例*/
var utils = ({
	addListener: null,
	removeListener: null,
	init: function() {
		if (typeof window.addEventListener === 'function') {
			this.addListener = function(el, type, fn) {
				el.addEventListener(type, fn, false);
			};
			this.removeListener = function(el, type, fn) {
				el.removeEventListener(type, fn, false);
			};
		} else if (typeof document.attachEvent === 'function') {
			this.addListener = function(el, type, fn) {
				el.attachEvent('on' + type, fn);
			};
			this.removeListener = function(el, type, fn) {
				el.detachEvent('on' + type, fn);
			};
		} else {
			utils.addListener = function(el, type, fn) {
				el['on' + type] = fn;
			};
			utils.removeListener = function(el, type, fn) {
				el['on' + type] = null;
			};
		}
		return this;
	}
}).init();
utils.addListener(document.body, "click", function() {
	console.log("Body has been loaded");
});
// 输出:
// Body has been loaded 
/*----------------------------END初始化时分支模式--------------------------------------*/

/*----------------------------START备忘模式--------------------------------------*/
/*
	备忘模式
		自定义属性的其中一个用途是缓存函数结果,因此在下一次调用该函数时就不用重做潜在的繁重计算。缓存函数结果也被称为备忘
*/
var someFun = function(params) {
	if (!someFun.cache[params]) {
		var result = {};
		result.startTime = new Date().getTime();
		for (var i = 100000000; i--;) {}
		result.endTime = new Date().getTime();
		result.message = params;
		someFun.cache[params] = result;
	}
	return someFun.cache[params];
};
someFun.cache = {};
console.log(someFun("first"));
console.log(someFun.cache);
console.log(someFun("second"));
console.log(someFun.cache);
console.log(someFun("first"));
console.log(someFun.cache);
console.log(someFun("second"));
console.log(someFun.cache);
// 输出:
// Object {startTime: 1371903727234, endTime: 1371903727398, message: "first"}
// Object {first: Object}
// Object {startTime: 1371903727399, endTime: 1371903727557, message: "second"}
// Object {first: Object, second: Object}
// Object {startTime: 1371903727234, endTime: 1371903727398, message: "first"}
// Object {first: Object, second: Object}
// Object {startTime: 1371903727399, endTime: 1371903727557, message: "second"}
// Object {first: Object, second: Object}

// 分析:
// 第一次调用someFun("first"),由于cache中并不存在,所以进行了计算,计算的结果存入到cache中
// 第一次调用someFun("second"),由于cache中并不存在,所以进行了计算,计算的结果存入到cache中
// 第一次调用someFun("first"),由于cache中存在,所以没有进行计算,直接取出,所以startTime和endTime为第一次计算的结果
// 第一次调用someFun("second"),由于cache中存在,所以没有进行计算,直接取出,所以startTime和endTime为第一次计算的结果

/*如果有多个参数,可以使用JSON先将参数对象*/
var someFun = function(firParam, secParam) {
	var cacheKey = JSON.stringify(Array.prototype.slice.call(arguments)),
		result;
	if (!someFun.cache[cacheKey]) {
		var result = {};
		result.startTime = new Date().getTime();
		for (var i = 100000000; i--;) {}
		result.endTime = new Date().getTime();
		result.message = firParam + secParam;
		someFun.cache[cacheKey] = result;
	}
	return someFun.cache[cacheKey];
};
someFun.cache = {};
console.log(someFun.cache);
console.log(someFun("Bill", "Gates"));
console.log(someFun.cache);
console.log(someFun("Bill", "Gates"));
// 输出:
// Object {}
// Object {startTime: 1371904542667, endTime: 1371904543172, message: "BillGates"}
// Object {["Bill","Gates"]: Object}
// Object {startTime: 1371904542667, endTime: 1371904543172, message: "BillGates"} 

/*----------------------------END备忘模式--------------------------------------*/

/*----------------------------START配置对象模式--------------------------------------*/
/*
	配置对象模式
		一种提供更整洁的API的方法,用一个对象作为参数代替大量的参数
		使用时需要传递大量的参数并不是很方便,一个更好的办法是仅使用一个参数对象来替代所有参数
		优点:
			不需要记住众多的参数以及其顺序
			可以安全忽略可选参数
			更加易于阅读和维护
			更加易于添加和删除参数
		缺点:
			需要记住参数名称
			属性名称无法被压缩
*/
/*----------------------------END配置对象模式--------------------------------------*/

/*----------------------------START  Curry化--------------------------------------*/
/*
	Curry化
		使函数理解并处理部分应用的过程
*/

function add(x, y) {
	var oldx = x,
		oldy = y;
	if (typeof oldy === 'undefined') {
		return function(newy) {
			return oldx + newy;
		};
	}
	return x + y;
}

console.log(add(3)(4));
console.log(add(3,4));
var add3 = add(3);
console.log(add3(10));
// 输出:
// 7
// 7
// 13
/*----------------------------END  Curry化--------------------------------------*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值