函数对象
- JavaScript中函数就是对象
- 函数对象连接的原型是Function.prototype
- 函数在创建时也有一个prototype,其拥有一个constructor属性,值是一个函数
函数显式创建
- 普通函数
function funcName( /*参数*/ ) { /*代码*/ }
- 匿名函数
function ( /*参数*/ ) { /*代码*/ }
调用
- 每个函数会接受两个附加的参数
- this
- this的值取决于调用的模式
- arguments
- 调用函数时参数的集合,实参和形参的个数不匹配时不会报错,如果参数值过多,超出的参数值会被忽略,反之,没有被赋值的参数默认为undefined
- this
- 四种调用模式
- 方法调用模式
- 将函数保存为一个对象的属性,也称为方法,该方法被调用时,this被绑定到该对象
- 函数调用模式
- 该函数不是一个对象的属性时,其是被当作一个函数来调用的
- 这种调用模式的this自始至终都与全局对象Window绑定(这是一个坑,请看代码注释)
- 构造器调用模式
- 在函数前带一个new来调用,即
new 函数名()
- 背后会创建一个对象,作为该函数prototype成员的新对象,this也是绑定到这个对象
- new前缀会改变return语句的行为
- 在函数前带一个new来调用,即
- Apply调用模式
- JavaScript是一门函数式编程语言,所以可以拥有方法
- apply方法接收两个参数
- 要绑定给this的值
- 参数数组
- 方法调用模式
/**
* 方法调用模式: 将函数保存为一个对象的属性,也称为方法,该方法被调用时,this被绑定到该对象
*/
var myObject = {
value: 0,
//increment是对象myObject的一个属性,也是一个函数
increment: function () {
//此处的this等同于对象myObject,即绑定
this.value += 1;
return this.value;
}
};
myObject.increment();//方法调用
/**
* 函数调用模式:
* 该函数不是一个对象的属性时,其是被当作一个函数来调用的
* 这种调用模式下的被调用函数的this自始至终都与全局对象Window绑定
*/
function add(val1, val2) {
return val1 + val2;
}
var myObject1 = {
value: 1,
/* 错误示范 */
double: function () {
var helper = function () {
//此处的this并不是与myObject1绑定,而是Window,所以并没有改变myObject1的value
this.value = add(this.value, this.value);
};
//函数调用模式,helper函数的this是全局对象
helper();
},
/* 修正后 */
double_fixed: function () {
//double_fixed是以方法模式调用的,所以this与myObject1绑定,可以用于操作myObject1的属性
var that = this;
var helper = function () {
//注意这是that不是this
that.value = add(that.value, that.value);
};
//函数调用模式
helper();
}
};
myObject1.double();
console.log(myObject1.value); //值是1
myObject1.double_fixed();
console.log(myObject1.value); //值是2
/**
* 构造器调用模式:
* 在函数前带一个new来调用,即new 函数名()
* 背后会创建一个对象,作为该函数prototype成员的新对象,this也是绑定到这个对象
* new前缀会改变return语句的行为(不过构造器一般没有返回值)
*/
var Quo = function (string) {
this.status = string;
// return new Boolean(false); new前缀会导致new Quo('confused)返回的是一个Boolean对象而不是Quo
}
Quo.prototype.get_status = function () {
return this.status;
}
//构造一个Quo实例
var myQuo = new Quo('confused');
//console.log(myQuo);
console.log(myQuo.get_status()); //值是confused
/**
* Apply调用模式:
* 在函数前带一个new来调用,即new 函数名()
* 背后会创建一个对象,作为该函数prototype成员的新对象,this也是绑定到这个对象
* new前缀会改变return语句的行为(不过构造器一般没有返回值)
*/
function mul(val1, val2) {
return val1 * val2;
}
var array = [3, 4];
var mul = mul.apply(null, array); //类似mul(3, 4)
console.log(mul); //值为12
//关于第一个参数的用法
var appObject = function () {};
appObject.prototype.get_money = function () {
return this.money;
}
var moneyObject = {
money: 1024
};
var money = appObject.prototype.get_money.apply(moneyObject);
console.log(money); //值为1024,即moneyObject的money成了appObject的属性,所以调用get_money时获取到了1024
参数arguments
- 可以理解为可变参数,接受任意多个参数
- 无论有没有形参,接收的参数在arguments都可以找到
var sum = function (arg1, arg2) {
console.log(arg1); //值是1
console.log(arg2); //值是2
var i, sum = 0;
for (i = 0; i < arguments.length; i += 1) {
sum += arguments[i];
}
return sum;
};
console.log(sum(1, 2, 3, 4, 5)); //值是15
扩充类型
JavaScript允许给语言的基本类型扩充功能,如之前的通过prototype添加方法,例子
Function.prototype.method = function (name, func) {
//基本类型的原型是公用结构,确保没有该方法时才添加
if (!this.prototype[name]) {
this.prototype[name] = func;
}
return this;
}
//给整数类型添加取整
Number.method('integer', function () {
return Math[this < 0 ? 'ceil' : 'floor'](this);
});
console.log((3.3).integer()) //3
console.log((-3.3).integer()) //-3
作用域
var a = 3;
var foo = function () {
var a = 4,//当foo被调用时,这里的a覆盖3
b = 5;
var bar = function () {
var b = 7,//当bar被调用时,这里的b覆盖5
c = 11;
a += b + c; //a = 4 b = 7 c = 11
};
bar();
console.log(a); //值为22
};
foo();
闭包
- 内部函数可以访问外部函数的参数和变量
- 但不包括this和arguments,就像之前看到的,在外部函数里以函数调用模式调用内部函数,内部函数的this是全局对象
- 内部函数拥有自己的arguments,所以无法[直接访问]外部函数的arguments,可以间接访问
- 外部函数的作用域对其他程序不可见
- 正面例子
//此处直接运行匿名函数: (function(){}())
//所以myObject是return返回的一个对象而不是一个函数
var myObject = (function () {
var value = 0;
return {
increment: function (inc) {
//访问外部函数的value
value += 1;
},
getValue: function () {
//访问外部函数的value
return value;
}
};
}());
console.log(myObject.value); //undefined ===> 无法直接访问到value
console.log(myObject.getValue()); //值为1
myObject.increment();
console.log(myObject.getValue()); //值为2
//关于之前利用prototype添加get_status方法的改善
//之前的做法 quo.prototype.get_status = function () {};
//现在的做法
var quo = function (status) {
return {
get_status: function () {
return status;
}
};
};
var myQuo = quo("amazed"); //利用闭关可以避免使用new创建对象
console.log(myQuo.get_status()); //值为amazed
- 反面例子
//这个函数原来预期每个节点被点击时显示不同的编号
//但实际上所有的值都是nodes.length
//因为onclick是直接访问外部的i,而不是复制i变量
//循环结束后,i = nodes.length,所以onclick访问i时值都是nodes.length
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (e) {
alert(i);
};
}
};
- 改良例子
var add_the_handlers_fixed = function (nodes) {
var i;
//i是形参,所以每次调用都有一个新的i被保留起来
//返回结果是点击事件需要的函数,利用闭包可以访问外部变量,直接alert(i)
var helper = function (i) {
return function (e) {
alert(i);
};
};
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = helper(i);
}
}
模块化
这是利用闭包实现的模块化,如果有Java经验就会发现,这等同于将成员变量私有化
/*
* 这个实现避免了seq和prefix成为全局变量
*/
var serial_maker = function () {
var prefix = ''; //私有化,外部无法访问
var seq = 0;
return {
set_prefix: function (p) {
prefix = String(p);
},
set_seq: function (s) {
seq = s;
},
gensym: function () {
var result = prefix + seq;
seq += 1;
return result;
}
}
};
var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
console.log(seqer.gensym()); //Q1000
console.log(seqer.gensym()); //Q1001
级联
在修改某些属性令方法返回this,得以继续操作对象
var myObject = function () {
var value = 0;
return {
init: function () {
value = 20;
return this;
},
add: function (val) {
//延伸问题:如果形参是value,与外部的value同名,那么该如何访问外部的value?
value += val;
return this;
},
increment: function () {
value += 1;
return this;
},
getValue: function () {
return value;
}
}
};
var obj = myObject();
var value = obj.init().add(1).add(2).increment().add(6).getValue();
console.log(value); //值为30
柯里化
第一次接触柯里化懵懵懂懂,柯里化函数就是将每次调用时的参数与原函数结合起来,产生了一个已有参数,又可以接收更多参数的函数,用以继续调用。这是非常浅的理解了。
Function.prototype.method = function (name, func) {
//基本类型的原型是公用结构,确保没有该方法时才添加
if (!this.prototype[name]) {
this.prototype[name] = func;
}
return this;
}
function add() {
var sum = 0;
for (var i = 0; i < arguments.length; i += 1) {
sum += arguments[i];
}
return sum;
}
Function.method('curry', function () {
var slice = Array.prototype.slice,
args = slice.apply(arguments),
that = this;
return function () {
return that.apply(null, args.concat(slice.apply(arguments)));
};
});
//add1已有参数1
var add1 = add.curry(1);
//add1已有参数1,2
add1 = add1.curry(2);
console.log(add1(2)); //值是5
记忆
将函数先前运算的结果保留,避免重复运算。这是一种思想,不是JavaScript特有的,在算法中称为动态规划。
/* 重复运算版本 */
var fibonacci = function (n) {
if (n < 2) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
var fibonacci_cache = function () {
var memo = [0, 1];
var fib = function (n) {
//如果memo[n]不是未定义,说明已经有结果,无需再次运算
var result = memo[n];
if (typeof result !== 'number') {
result = fib(n - 1) + fib(n - 2);
memo[n] = result;
}
return result;
};
return fib;
}();
console.log(fibonacci(5));
console.log(fibonacci_cache(6));