JavaScript中的函数

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值