1.函数的五种声明
1.1具名函数
function f(x,y){
return x + y
}
f.name // 'f'
1.2匿名函数
var f = function (x,y){
return x + y
}
f.name // 'f'
如果此时执行f = 1,那么这个匿名函数就失去了与栈内存的联系,会被浏览器回收。
1.3具名函数赋值
var f = function f2(x,y){
return x + y
}
f.name // 'f2'
console.log(f2) //报错,无定义
1.4 window.Function
var f = new Function ('x', 'y', 'return x + y') //new可以省略
f.name // 'anonymous'
1.5箭头函数(声明的一定是匿名函数)
var f = (x,y) => {return x + y}
var sum = (x,y) => x + y //若只有一句话,并且不返回对象,可以同时去掉return和{}
var n1 = n => n * n //若只有一个参数,可以省略()
举一个栗子:
var n = 1;
var f = Function ('x', 'y', 'return x +' + n + '+ y')
f(1, 2) // 4
// <=> 'return x +' + 1 + '+ y'<=>'return x +' + '1' + '+ y'<=>'return x + 1 + y'
//如果 var n = 'a',会报错a未定义,需改成 var a = "'a'"
非箭头函数在声明一定会绑定一个内置的 this对象,这是javascript的语法决定的!
2.函数的本质
所以整个函数体是以字符串的形式存储在heap内存中的,通过__proto__属性找到原型对象中的call()方法对函数体进行执行。eval()有着同样的效果,可以将字符串转成代码执行。
eval('1+1') //2
var a = "console.log(1)"
a // "console.log(1)"
eval(a) // 1
3. JS中函数无签名和函数提升
ECMAScript 中没有函数签名的概念,因为其函数参数是一个包含零或多个值的伪数组的形式传递的——arguments,因此ECMAScript函数不能重载。
如果定义了很多相同函数名的函数,则该函数名只属于后定义的函数。
JS中的函数声明提升(整个函数体提前)是优于变量声明提升(赋值操作最后做)的:
console.log(f1); // function f1() {} 函数
console.log(f2); // undefined
var f1 = 3
function f1() {}
console.log(f1) // 3 ,参考函数的本质
var f2 = function() {}
console.log(f2)
其实它等价于代码:
function f1() {}
var f1
var f2
console.log(f1);
console.log(f2);
f1 = 3
console.log(f1)
f2 = function() {}
console.log(f2)
PS: 由于函数变量提升,所以不要在if 和 try 语句中使用函数,但可以用函数表达式。
4. JS中函数的调用
f(1, 2) <=> f.call (undefined, 1 ,2)
4.1 this和arguments
对于上式来说,this 就是 call 传递的第一个参数undefined, arguments就是伪数组[1 ,2]
具体来说:'use strict’严格模式下,this就是call()的第一个参数,而在非严格模式下,浏览器会将其改为window对象:
undefined => window
666 => Number(666)
false => Boolean(false)
uuu => String(uuu)
String => f String (){}
this的更多讲解参考我的另一篇博客
4.2 函数的 call stack 调用栈
它是函数的一个先进后出的数据结构,和内存途中的stack不是一回事
a、栈是有容量的,所以函数进行大量的递归操作,会导致栈溢出错误
b、每调用一次函数,压一次栈
call stack 示意: 函数嵌套调用
4.3 函数的作用域(tree数据结构)与面试题
4.3.1 a = 3 的内涵
首先是一个赋值操作,在当前作用域(或向上搜索)找到变量 a 进行赋值,如果找不到才是声明全局变量a并赋值
4.3.2 var a = b = 3 的内涵
var a;b =3 ; a = b 或者 var a = ( b = 3 ),即 b 是未声明的
4.3.3 面试题1
var obj1 = { p: 1 }, obj2 = [1, 2, 3]
function f1(o) {
o.p = 2;
}
function f2(o) {
o = [2, 3, 4];
}
f1(obj1); // obj1 = { p: 2}, 原始值已经更改
f2(obj2); // obj2 = [1, 2, 3] ,未改变
4.3.4 面试题2
var a = 1;
function f1() {
console.log(a) // undefined,函数内部也会在内部作用域进行变量提升的
var a = 2;
f2.call();
}
function f2() {
console.log(a); // 1
}
f1.call();
注意f2不是在f1里面声明的,所以整个作用域是(全局f1,f2,a=1),之后是(f1:a=2)和(f2:无)的作用域,两个是独立的。
4.3.5 面试题3
var liTags = document.querySelectorAll('li');
for(var i=0;i<liTags.length;i++){
liTags[i].onclick = function () {
console.log(i);
}
}
// 问点击第3个li时, 打印几? 6
// for循环在你点击之前就执行完了, 在这个作用域中i=6
4.3.6 面试题4
var obj = {
foo: function(){
console.log(this)
}
}
var bar = obj.foo
obj.foo() // obj,因为转换为 obj.foo.call(obj),this 就是 obj
bar() // window,转换为 bar.call(),由于没有传东西
// 所以 this 就是 undefined,最后浏览器给你一个默认的 this —— window 对象
转换代码非常有用
5. 闭包
5.1 闭包的定义
var a = 1;
function f4() {
console.log(a);
}
// 若一个函数使用了它作用域外的变量,那 (这个函数+变量) 就是闭包。
5.2 闭包的作用
5.2.1 函数外部读取函数内的局部变量
function f1() {
var n = 666;
function f2() {
console.log(n);
}
return f2;
}
var result = f1(); //注意,result拿到的是函数f2的地址
result(); // 666
5.2.2 在内存中保存变量,避免全局变量的污染
function increase(start) {
return function () {
return start++;
}
}
var inc = increase(1);
inc() // 1
inc() // 2
inc() // 3
5.2.3 在对象中创建私有变量
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('Michael');
p1.setAge(18);
p1.getAge() // 18
p1.name // Michael
5.3 闭包的不足
由于闭包会使得函数中的变量会被保存在内存中,因常驻在内存中,如果变量对象不使用,会比较占用内存,在IE8以下浏览器会造成内存泄露
6.立即执行函数
6.1 立即执行函数的定义
立即执行函数就是在匿名函数定义后立即执行,下面用代码表示
(function(){ /* code */ }());
(function(){ /* code */ })();
-function(){ /* code */ }();
+function(){ /* code */ }();
~function(){ /* code */ }(); // 以上都不推荐使用
!function(){ /* code */ }(); // 推荐使用
6.2 立即执行函数的作用
声明一个局部变量,防止变量污染,经常和闭包一起使用。
在ES6中,使用 let 就可以创建出局部变量了,代码如下:
{
let a = 1
}
console.log(a) // 报错,a 没有定义
6.3 立即执行函数与闭包的栗子
// script1.js
! function (){
var person = {
name: 'frank',
age: 18
}
window.frankGrowUp = function (){
person.age += 1
return person.age
}
}.call()
// script2.js
frankGrowUp() // 19
frankGrowUp() // 20
var a = person.age // 报错,person无定义
- 立即执行函数使得 person 无法被外部访问
- 闭包使得匿名函数可以操作 person
- window.frankGrowUp保存了匿名函数的地址
- 任何地方都可以使用 window.frankGrowUp
=> 任何地方都可以使用 window.frankGrowUp 操作 person,但是不能直接访问 person