这篇博客只是给出了使用js中的函数的代码片段,并没有详解js中的函数,具体详解请参考《js权威指南》
1.1函数的定义
//输出object的每个属性的名称和值,返回undefined
function printprops(object) {
for (var key in object) {
console.log(key + ":" + object[key] + '\n');
}
}
//计算两个笛卡尔坐标之间的距离
function distance(x1, y1, x2, y2) {
var dx = x1 - x2;
var dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
}
//利用函数表达式定义一个函数
var square = function (x) {
return Math.pow(x,x);
}
1.2当函数中没有return返回值的情况
var object = {
name: 'zane',
age: 18
};
//定义一个函数
function printprops(object) {
for (var key in object) {
console.log(key + ':' + object[key] + '\n');
}
}
//1.直接调用函数
printprops(object);
//2.在控制台打印‘调用函数后’的结果
//由于printprops没有return返回值,所以下面这条语句,先调用了printprops函数,
//而后因为没有return返回值,故在控制台输出了undefined.
//而在这里一定要搞清楚,是在控制台输出调用函数后的结果还是直接在控制台打印这个函数
//目的不同,那么其结果也会不同。
console.log(printprops(object));
//3.打印函数,看是什么结果?
//结果就是在控制台输出了这个函数的字符串表示,
//字符串这种数据类型在控制台输出的时候,默认的字体颜色是黑色的。
console.log(printprops);
2.1函数作为普通函数调用
//下面这条语句是什么鬼
//var strict = (function () {return !this;})();
// var strict = (function () {return !this;}());
//这两条语句的执行的结果是相同的,都输出了false;
//在非严格模式下,this(又称为调用上下文)的值是全局对象,在严格模式下,其值是undefined
//下面这条语句表示,在非严格模式下,this的值是全局对象,那么其boolean的值是true,取反之后便会返回false
//若在严格模式下,this的值是undefined,其布尔值是false,取反之后是true,返回值便是true.
//可以用下面这条语句来测试当前脚本运行的模式是否是严格模式,如果返回true,则为严格模式;返回false,则为非严格模式。
var strict = (function () {return !this;})();
console.log(strict);
2.2函数作为方法调用
var o = {
m: function () {
var self = this;
console.log(this === o); //在控制台输出true
f(); //调用函数f ,在这里f是作为普通函数调用的
function f() {
console.log(this === o); //在控制台输出false,这里的this是指全局对象
console.log(self === o); //在控制台输出true,self就是对象o
}
}
}
//调用这个对象o中的方法m
o.m();
3.1将对象属性用作实参
/**
*
* @param from{Array}
* @param from_start{index}
* @param to{Array}
* @param to_start{index}
* @param length{number}
*/
function arraycopy(from, from_start, to, to_start, length) {
//这里写逻辑代码
}
/**
*
* @param args{object}
*/
function easycopy(args) { //定义了一个函数,引入一个形参对象,通过这个函数调用arraycopy这个函数
arraycopy(
args.from,
args.from_start || 0,
args.to,
args.to_start || 0,
args.length
);
}
var a = [1, 2, 3, 4], b = [];
var args = {
from: a,
to: b,
length: 4
}
easycopy(args);
3.2实参类型
//这个函数可以接收任意数量的实参
function flexisum(a) {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
var element = arguments[i], n;
if (element == null) continue; //忽略值为null和undefined的情况,结束本次循环
if (element instanceof Array) //如果实参是数组
n = flexisum.apply(this, element); //递归地计算累加和
else if (typeof element == 'function') //如果参数是函数类型
n = Number(element()); //调用这个函数,并做类型转换
else
n = Number(element); //直接做类型转换
if (isNaN(n)) //如果无法转换为数字,则抛出错误
throw Error("flexisum():can't convert" + element + "to number");
total += n;
}
return total;
}
4.1作为值的函数
//定义一些简单的函数
function add(x) {
return x + y;
}
function subtract(x, y) {
return x - y;
}
function multiply(x, y) {
return x * y;
}
function divide(x, y) {
return x / y;
}
//下面的函数以上面的某个函数作为参数
//并给它传入两个操作数,然后调用它
function operate(operator, operand1, operand2) {
return operator(operand1, operand2);
}
//折行代码所示的函数调用实际上计算了(2+3)+(4*5)的值。
var i = operate(add, operate(add, 2, 3), operate(multiply, 4, 5));
//我们为这个例子重复实现一个简单的函数
//这次实现使用函数直接量,这些函数直接量定义在一个对象直接量中
var operators = {
add: function (x, y) {
return x + y;
},
substract: function (x, y) {
return x - y;
},
multiply: function (x, y) {
return x * y;
},
divide: function (x, y) {
return x / y;
},
pow:Math.pow //使用预定义的函数
}
//这个函数接收一个名字作为运算符,在对象中查找这个运算符
//然后将它作用于所提供的操作数
//注意这里调用运算符符函数的语法
function operate2(operation,operand1,operand2) {
if(typeof operators[operation] === 'function')
return operators[operation](operand1,operand2);
else throw 'unknown operator';
}
//试计算('hello'+' '+'world')
var j=operate2('add','hello',operate2('add',' ','world'));
//使用预定义的函数Math.pow()
var k = operate2('pow',10,2);
4.2自定义函数属性
//将函数看成一个对象,因此可以自定义属性
//初始化函数对象的计数器属性
//由于函数声明被提前了,因此这里是可以在函数声明之前给他的成员的赋值的
uniqueInteger.counter = 0;
//每次调用这个函数都会返回一个不同的整数
//它使用一个属性来记住下一次将要返回的值
function uniqueInteger() {
return uniqueInteger.counter++;//先返回计时器的值,然后计数器再自增1
}
//计算阶乘,并将结果缓存至函数的属性中
function factorial(n) {
if (isFinite(n) && n > 0 && n == Math.round(n)) {
if (!(n in arguments.callee))
arguments.callee[n] = n * arguments.callee(n - 1);
return arguments.callee[n];
}
else return NaN; //如果输入有误,则返回NaN
}
factorial[1] = 1;//初始化缓存以保存这种基本情况
5.1作为命名空间的函数
//案例函数:特定场景下返回带补丁的extend()版本
//定义一个扩展函数,用来将第二个以及后续参数复制到第一个参数
//这里我们处理了IE bug:在多数IE版本中,
//如果o的属性拥有一个不可枚举的同名属性,则for/in循环
//不会枚举对象o的可枚举属性,也就是说,将不会正确地处理诸如toString属性
//除非我们显示地检测它
var extend = (function () { //将这个函数的返回值赋给extend
//在修复它之前,首先检查是否存在bug
//这个for循环的作用是检测浏览器是否会处理这个bug,如果处理这个bug,则执行下面这段代码
//如果不处理这段bug,那么下面这段代码将不会被执行,将会跳到后面的代码处,执行后续的代码
//至于为什么不会执行这段代码,是因为bug导致浏览器不会枚举具有同一属性名的可枚举的属性。
// 因此,如果有bug就不会执行for/in循环里面的代码
for (var p in {toString:null}){
//如果代码执行到这里,那么for/in循环会正确执行并返回
//一个简单版本的extend()函数
return function extend(o) {
for (var i = 0; i < arguments.length; i++) {
var source =arguments[i];
for (var prop in source) o[prop]=source[prop];
}
return o;
};
}
//如果代码执行到这里,说明for/in循环不会枚举测试对象的toString属性
//因此返回另一个版本的extend()函数,这个函数显示测试
//Object.prototype中的不可枚举属性
return function patched_extend(o) {
for (var i = 0; i < arguments.length; i++) {
var source=arguments[i];
//复制所有的可枚举属性
for(var prop in source) o[prop] = source[prop];
//现在检查特殊性
for (var j = 0; j < protoprops.length; j++) {
prop=protoprops[j];
if(source.hasOwnProperty(prop)) o[prop]=source[prop];
}
}
return o;
};
//这个列表列出了需要检查的特殊属性
var protoprops =['toString','valueOf','constructor','hasOwnProperty','isPrototypeOf','toLocaleString'];
}());
6.1闭包
//比较下面这两个函数的调用结果
//这里为了使变量不冲突,将这段代码放到了匿名函数中,
//在匿名函数中来模拟全局作用域
(function () {
var scope='global scope'; //全局变量
function checkscope() {
var scope = 'local scope'; //局部变量
function f() {
return scope; //在作用域中返回这个值
}
return f(); //返回调用f()的结果
}
checkscope(); //local scope;
});
(function () {
var scope='global scope'; //全局变量
function checkscope() {
var scope = 'local scope'; //局部变量
function f() {
return scope; //在作用域中返回这个值
}
return f; //返回函数
}
checkscope()(); //local scope;
})
6.1.1利用闭包定义一个计数器
function counter(n) {
return { //返回一个对象
get count(){return n++}, //获取这个对象的属性
set count(m){ //设置这个对象的属性
if(m>=n) n=m;
else throw Error('count is only set to be a large value');
}
};
}
var c=counter(1000);
c.count //1000
c.count //1001
c.count=2000
c.count //2000
c.count=2000 //Error 原因:c.count=2000 ,设置之后n的值为2000,获取对象的count值为2000
//但是,此时n已经自增,所以再次给对象的属性count赋值为2000,便会显示错误
6.2利用闭包实现的私有属性存取器的方法
//这个函数给对象o增加了属性存取器方法
//方法名称为get<name>和set<name>。如果提供了一个判定函数
//setter方法就会用它来检测参数的合法性,然后再存储它
//如果判定函数会返回false,setter方法会抛出一个异常
//这个函数有一个非同寻常之处,就是getter和setter函数
//所操作的属性值并没有存储在对象o中
//相反,这个值仅仅是保存在函数中的局部变量之中
//getter和setter方法同样是局部函数,因此可以访问这个局部变量
//也就是说,对于两个存取器方法来说,这个变量是私有的
//没有办法绕过存取器方法来设置或修改这个值
function addPrivateProperty(o, name, predicate) {
var value; //这是一个属性值
//getter方法简单地将其返回
o['get' + name] = function () {
return value;
};
//setter方法首先检查值是否合法,若不合法就抛出错误
//否则就将其存储起来
o['set' + name] = function (v) {
if (predicate && !predicate(v))
throw Error('set' + name + ':invalid value' + v);
else
value = v;
};
}
//展示addPrivateProperty()方法
var o={}; //设置一个空对象
//增加属性存取器方法getName()和setName()
//确保只允许字符串值
addPrivateProperty(o,'Name',function (x) {
return typeof x == 'string';
});
o.setName('Frank'); //设置属性值
console.log(o.getName()); //获取属性值
7.1函数的length属性
//这个函数使用arguments.callee,因此它不能在严格模式下运行
function check(args) {
var actual = args.length; //实参的真实个数
var expected = args.callee.length; //期望的实参个数
if (actual !== expected) //如果不同,则抛出异常
throw Error("Expected" + expected + "args;got" + actual);
}
function f(x, y, z) {
check(arguments); //这个arguments是函数f的arguments
return x + y + z;
}
7.2函数的call()方法和apply()方法
//将对象o中名为m()的方法替换为另一个方法
//可以在调用原始方法之前和之后记录日志消息
function trace(o, m) {
var original = o[m]; //在闭包中保存原始方法
o[m] = function () { //定义新的方法
console.log(new Date(), "Entering:", m); //输出日志消息
var result = original.apply(this, arguments); //调用原始函数
console.log(new Date(), "Exiting:", m); //输出日志消息
return result; //返回结果
}
}
7.3bind()方法
//bind()方法可以将某个函数绑定至一个对象,称为该对象的一个方法
function f(y) {
return this.x + y;
}
var o = {x: 1};
var g = f.bind(o); //将函数f绑定到对象o上,称为对象o的方法
g(2) //3
//一下的代码可以实现这种绑定
//返回一个函数,通过调用它来调用o中的方法f(),传递它所有的实参
function bind(f, o) {
if (f.bind) return f.bind(o); //如果函数f中存在方法bind(),那么直接返回
else return function () { //否则,这样绑定
return f.apply(o, arguments);
}
}
//函数柯里化(currying)
//除了第一个实参之外,传入bind()的实参也会绑定至this,这种附带的应用称为函数柯里化
var sum = function (x, y) {
return x + y;
}
//创建一个类似sum的新函数,但this的值绑定到null
//并且第一个参数绑定到1,这个新的函数期望只传入一个实参
var succ = sum.bind(null, 1);
succ(2); //3:x绑定到1,并传入2作为实参y
function f(y, z) {
return this.x + y + z;
}
var g = f.bind({x: 1}, 2); //绑定this和y
g(3)
//ECMAscript3版本的Function.bind()方法
if (!Function.prototype.bind) {
Function.prototype.bind = function (o /*,args*/) {
//将this和arguments的值保存至变量中
//以便在后面嵌套的函数中可以使用它们
var self = this, boundArgs = arguments;
//bind()方法的返回值是一个函数
return function () {
//创建一个实参列表,将传入bind()的第二个及后续的实参都传入这个函数
var args = [], i;
for (i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
for (i = 0; i < arguments.length; i++) args.push(arguments[i]);
//现在将self作为o的方法来调用,传入这些实参
return self.apply(o, args);
};
};
}
7.4Function()构造函数
//虽然Function构造函数在实际编程中很少用到,但这里我们还是要简单介绍
//不管是通过函数定义语句还是函数直接量表达式,函数的定义都要使用function关键字
//但函数还可以通过Function()构造函数来定义,例:
var f = new Function('x', 'y', 'return x*y;');
//上面折行代码等价于下面折行代码
function f(x, y) {
return x * y;
}
//关于Function构造函数最重要的一点是:就是它创建的函数并不是使用词法作用域,相反,函数体代码的编译
//总是会在顶层函数执行,如下面代码所示:
var scope='global';
function construcFunction() {
var scope = "local";
return new Function("return scope"); //无法捕获局部作用域
}
//调用construcFunction
construcFunction()(); //返回值为全局变量global
8.1使用函数处理数组
//非函数式编程实现对数组的处理
(function () {
var data = [1, 1, 3, 5, 5];
var total = 0;
for (var i = 0; i < data.length; i++) { //对数组元素求和
total += data[i];
}
var mean = total / data.length; //求total的平均数
//计算标准差:首先计算每个数据减去平均数之后偏差的平方,然后求和
total = 0;
for (var i = 0; i < data.length; i++) {
var deivation = data[i] - mean;
total += deivation * deivation;
}
var stddev = Math.sqrt(total / (data.length - 1));
});
//利用函数式编程方法处理数组
(function () {
//首先定义两个简单的函数
var sum = function (x, y) {
return x + y;
};
var square = function (x) {
return x * x;
};
//然后将这些函数和数组方法配合使用
var data = [1, 1, 3, 5, 5];
var mean = data.reduce(sum) / data.length;
var deviations = data.map(function (x) {
return x - mean;
});
var stddev = Math.sqrt(deviations.map(square).reduce(sum) / (data.length - 1));
});
//map()方法和reduce()方法详解
//map()方法和reduce()方法是数组的两个方法
//因此其对象是每个数组元素
//对于每个数组元素调用函数f(),并返回一个结果数组
var map = Array.prototype.map
? function (a, f) { //如果存在这个方法,则直接使用它
return a.map(f);
} : function (a, f) {
var results = [];
for (var i = 0, len = a.length; i < len; i++) {
if (i in a) results[i] = f.call(null, a[i], i, a);
}
return results;
};