1.复制变量值
在说函数前,我觉得需要先说说关于变量值的复制,以便后面的理解。
复制基本类型的值:
当一个变量复制另外一个值为基本类型的变量时,两个变量可以参与任何操作而不会互相影响
var num1 = 5; var num2 = num1; console.log('num1的值是:' + num1); // 5 console.log('num2的值是:' + num2); // 5 num1 = 10; console.log('num1修改后,num1的值是:' + num1); // 10 console.log('num1修改后,num2的值是:' + num2); // 5
复制引用类型的值:
当一个变量复制另外一个值为引用类型的变量时,改变其中一个变量的值,就会影响另外一个变量
var obj1 = { name: 'Mike' }; var obj2 = obj1; console.log('obj1.name:' + obj1.name); // Mike console.log('obj2.name:' + obj2.name); // Mike obj1.name = 'Alice'; console.log('改变后的obj1.name:' + obj1.name); // Alice console.log('改变后的obj2.name:' + obj2.name); // Alice
var arr1 = [1,2,3]; var arr2 = arr1; console.log(arr1); // [1,2,3] console.log(arr2); // [1,2,3] arr2[3] = 4; console.log('改变后的arr1:' + arr1); // [1,2,3,4]
console.log('改变后的arr2:' + arr2); // [1,2,3,4]
2.函数声明与函数表达式
add(10,10); // 20 function add(num1,num2) { return num1 + num2; }
以上是函数声明,函数声明的方式创建的函数会提前
add(10,10); // Error var add = function(num1,num2) { return num1 + num2; }
以上是函数表达式,函数表达式的方式创建的函数不会提前
3.传递参数
函数的参数,其实可以看成是一个局部变量。所以当我们传递参数,特别是当参数是一个对象的时候,我们需要注意执行函数后,对原来值的影响。
var count = 10; function add(num) { num += 10; console.log(num); } add(count); // 20 console.log(count); // 10
对于上面的代码,可以看成是传递的参数是一个数值之类的基本类型值的时候,var num = count,然后按照基本类型值传递。
var person = { name: '小红' }; function setName(obj) { obj.name = '小明'; console.log(obj.name); // '小明' } setName(person); console.log(person.name); // '小明'
当传递的参数是一个对象时,此时var obj = person,两个变量都指向同一个对象,改变obj.name就会改变person.name,所以按照引用类型传递
var person2 = { name: '花花' }; function setName2(obj) { obj.name = '豆豆'; console.log(obj.name); // '豆豆' console.log(person2.name); // '豆豆' obj = { name: '黑黑' }; console.log(obj.name); // '黑黑' } setName2(person2); console.log(person2.name); // '豆豆'
首先是var obj = person2,两个变量都指向同一个对象,所以按照引用类型传递。
然而重写obj后,obj指向了另外的一个对象,所以不会再次改变对象person2。
4.arguments对象
arguments对象表示函数传入的所有参数,不是定义函数时的写的参数:
function fun1(num1,num2,num3) { return arguments.length; } fun1(1); // 1 function fun2(num1,num2) { return arguments.length; } fun2(22,33); // 2
arguments拥有length属性,不过arguments只是一个伪数组,并非一个真正的数组。
arguments还有一个callee属性,该属性指向拥有这个arguments对象的函数,arguments.callee()相当于调用函数本身,用它可以实现更松散的耦合:
function fun1(num) { if (num <= 1) { return 1; } else { return num * fun1(num-1); } } function fun2(num) { if (num <= 1) { return 1; } else { return num * arguments.callee(num-1); } } // 实现更松散的耦合 var fun3 = fun2; fun2 = function() { return 0; }; console.log(fun3(5)); // 120 console.log(fun2(5)); // 0
从上面的代码可以看出,即使重写了函数fun2,函数fun3也能打印正确的值。
函数对象还有一个caller属性,表示调用当前函数的函数:
function fun1() { fun2(); } function fun2() { console.log(arguments.callee.caller); } fun1(); // 打印出函数fun1
5.apply()、call()和bind()
先说用法,apply()方法接受两个参数,第一个是运行函数的作用域,第二个是函数的参数数组,可以是Array的实例,也可以是arguments对象。call()用法与apply()一样,不过传递第二个参数时,写法有些差别。
window.color = 'blue'; var obj = { color: 'red' }; function fun1(num) {
console.log(this.color);
return num; } function fun2() {
return fun1.apply(this,arguments);
}
fun1.apply(obj); // red
fun2(2); // blue 2
当apply()的第一个参数是this的时候,则this表示的是当前函数所在的作用域,这里是在window作用域下,所以fun2函数的this.color是blue。
对于apply()、call()和bind()3个方法,我所知道的真正强大的地方就是在于能够扩充函数赖以运行的作用域。
他们的区别是bind 是返回对应函数,便于稍后调用,apply 、call 则是立即调用 :
var obj = { age: 21, sayAge: function () { return this.age; } }; var copySay = obj.sayAge.bind(obj); copySay(); // 21 注意这里加了括号 var copySay2 = obj.sayAge.apply(obj); copySay2; // 21 注意这里没加括号
关于apply()和call()更详细的用法与区别,大家可以看看其他前辈的博客,很多也很详细,在这里我就不再赘述,只是通过自己的理解,记录了下红皮书上的大体和关键点。