JS里的函数

函数的声明方式

  1. 具名函数

    function x(input1, input2) {
    return undefined;
    };

    x.toString(); //以及其他7种方法
  2. 匿名函数

    var x;
    x = function(input1, input2) {
    return undefined;
    };
  3. 变量加具名函数

    var x = function y (input1, input2) {
    return undefined;
    };

    console.log(y); // 这个 y 是 undefined

但是

function y(...) {...}
console.log(y) // 这个是可以的

这个是不一致造成的,因为前者的作用域在函数体内

  1. window.Function 函数对象(并不常用)
    f = new Function('x', 'y', 'return x+y');

以下是例子

var n = 1;
f = new Function('x', 'y', 'return x+' + n + '+y');

那么 f(1, 2) 的值是多少呢,就是 4

  1. 箭头函数表示
    f = (x, y) => {return x+y}

也可以表示为

f = (x, y) => x+y

比如

n2 = n => n*n
n2(4) // 16

一个小知识点,所有的函数都个属性叫做 name

function f(){}
f.name // 'f'

然鹅匿名函数

var f2 = function(){}
f2.name // 'f2'

然鹅

var f3 = function f4(){}
f3.name // 'f4'

然鹅

f5 = new Function('x', 'y', 'return x+y')
f5.name // 'anonymous' 这里居然是匿名...

调用函数

function f(x, y) {
return x + y
}

f // f 是个变量,不会执行到 f 函数

比如一个求面积的函数

function getTriArea(width, height) {
var n = width * height;
var m = n / 2;
return m;
};
getTriArea(4, 3); // 6

那么在内存里面,函数是怎么存的呢, 试着用纯对象来模拟函数

var f = {}
f.name = 'f'
f.params = ['x', 'y'] // 函数参数
f.fbody = 'console.log(1)' // 函数体
f.call = function() {
return window.eval(f.fbody);
}

// 调用以上代码
f.call() // 1

f 是这个对象,而 f.call() 是执行这个函数体的代码

可以执行代码的对象就叫做函数

JS 有 7 种数据类型

number string boolean null undefined symbol object

日常复习的原型结构图

一般而言,有两种调用方法

f(1, 2)
f.call(undefined, 1, 2)

只有下面那种才是真正的调用

function f(x, y) {
return x + y
}

f.call(undefined, 1, 2) // 参数从第二个开始传

this 和 arguments

以上面的例子

f.call(undefined, 1, 2) // 3
-----------------------------
| | |
+ + +
this [1, 2] arguments

1. call 的第一个参数可以用 this 得到
2. call 的后面的参数可以用 arguments 得到

普通模式下,如果传入的第一个参数是 undefined 或不传,那么 this
值就是 window,如果用严格模式 'use strict',那么 this
值就是 undefined, this 就是 call 的第一个参数

什么是伪数组,就是 __proto__ 中没有指向 Array.prototype

call stack 调用栈

以下是一个较为复杂的例子

function a() {
console.log('a1')
b()
console.log('a2')
return 'a'
}
function b() {
cosole.log('b1')
c()
console.log('b2')
return 'b'
}
function c() {
console.log('c')
return 'c'
}

a.call()
console.log('end')

// a1 b1 c b2 a2

下图是对上图的解释

再一个例子,用递归

function sum(n) {
if (n === 1) {
return 1
} else {
// return n + sum(n-1)
return n + sum.call(undefined, n-1)
}
}

sum.call(undefined, 5) // 1 + 2 + 3 + 4 + 5

作用域

var a = 1
function f1() {
var a = 2
f2.call()
function f2() {
var a = 3
console.log(a)
}
}

f1.call()
console.log(a)

见下图

本质上还是就近原则

以下对代码进行变形

var a = 1
function f1() {
// 这里进行代码的改动
f2.call()
console.log(a)
var a = 2 // 变量提升,先把声明提上去
function f2() {
var a = 3
console.log(a)
}
}

f1.call()
console.log(a)

浏览器在执行代码时,会先找变量,后执行程序逻辑,以上代码在进行
变量提升后就相当于

var a = 1
function f1() {

function f2() {
var a = 3
console.log(a)
}
var a
f2.call()
console.log(a) // undefined
a = 2 // 变量提升,先把声明提上去
}

f1.call()
console.log(a)

又一个例子

var a = 1
function f1() {
console.log(a)
var a = 2
f4.call()
}

function f4() {
console.log(a) // a = ??
}

f1.call()
console.log(a)

以上代码做变量提升后为

var a = 1

function f1() {
var a;
console.log(a) // 这里的 a 是 undefined
a = 2
f4.call()
}

function f4() {
console.log(a) // 这里的 a 是 1 ,因为这个 a 跟 f4 这个作用域有关,或者跟它的父作用域有关
}


f1.call()
console.log(a)

再再再一个例子

var a = 1

function f1() {
var a;
console.log(a) // 这里的 a 是 undefined
a = 2
f4.call()
}

function f4() {
console.log(a) // 下面的 ??? 代码,使得这里的 a = 2
}

// ???
a = 2 // 这里修改了 a 的值

f1.call()
console.log(a)

再再再再一个例子

<ul>
<li>选项1</li>
<li>选项2</li>
<li>选项3</li>
<li>选项4</li>
<li>选项5</li>
<li>选项6</li>
</ul>

var liTags
var i
liTags = document.querySelector('li')
for (i = 0; i < liTags.length; i++) {
liTags[i].onclick = function() {
console.log(i) // 一个小时之后
}
}

// for 循环之后,i 就是 6
console.log(i)

为什么是这样呢,一个”合理”的解释是,在执行 onclick 函数之前,这个 i 已经跑完了 for 循环了。 function 里面的 i 会跑去寻找它父作用域的已经声明并且赋值过的 i,由于程序运行的速度很快,我们可以理解为,在执行 function 里面的函数时,此时 i 已经变成 6 了

闭包

如果一个函数使用了范围外的值,那么这个(函数 + 这个变量)就叫闭包

var a = 1
function f4() {
console.log(a)
}

注意

/######################################/
var f1 = function f2(){}
console.log(f2) // f2 is not defined(f2 不存在,而且 f2 不是 undefined)

/######################################/
function f(){
console.log(this)
}
f.call(1) // Number 对象 1

/######################################/
function f(){
'use strict'
console.log(this)
}
f.call(1) // 1

/######################################/
function f(){
console.log(this)
}
f.call() // 打印 Window

/######################################/
function f(){
'use strict'
console.log(this)
}
f.call() // 打印 undefined

/######################################/
function f(){
return 1
}
a = f // a is function f

/######################################/
function f(){
return 1
}
var a = f.call() // a = 1

/######################################/
function f(){
console.log(this)
}
f.call() // 打印的是 window


/######################################/
var a = 1,2 //报错
var a = (1,2) // a = 2 最后一个
var a = (1, console.log(2)) // a 为 undefined

/######################################/
function f1(){
console.log(this)
function f2(){

}
}
var obj = {name: 'obj'}
f1.call( obj ) // 打印 obj 对象

/######################################/
function f1(){

function f2(){
console.log(this)
}
f2.call()
}
var obj = {name: 'obj'}
f1.call( obj ) // Window 对象

/######################################/
function f1(){
console.log(this) // 第一个 this
function f2(){
console.log(this) // 第二个 this
}
f2.call()
}
var obj = {name: 'obj'}
f1.call( obj ) // 先打印 obj 对象,然后打印 Window 对象

// this 就是 call 的第一个参数,第一个 this 对应的 call 是 f1.call(obj),第二个 this 对应的 call 是 f2.call()
// this 和 arguments 都是参数,参数都要在函数执行(call)的时候才能确定