JS进阶 - 函数

1. 定义

  1. 匿名函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function (){
return 1
}
// 这就是一个匿名函数,但是会报错,声明但是不引用,浏览器不让你过

var fn = function (){
return 1
}
// 给他一个引用,存一个对象的堆内存所在的地址

var fn2 = fn
// 将 fn 的引用再复制给 fn2

fn.name // fn
fn2.name // fn

//他是匿名函数,但是有 name
  1. 具名函数
1
2
3
4
5
6
7
8
9
function fn3 (){
return 3
}
// 所有地方都能访问到 fn3

var fn4 = function fn4 (){
return 4
}
// 作用域只在函数本身
  1. 箭头函数
1
2
3
4
5
6
7
var fn5 = e => e+1;
fn5(4); //5
// 最简单的,自身 +1

var fn6 = (i,j) => i+j;
var fn6 = (i,j) => {console.log(1); return i+j};
// 在 this 处有区别

三个函数前两个区别在于名称,实际都有 .nam

第三个函数特殊之处在于 this

2. 词法作用域

词法作用域也作静态作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var global = 1
function fn1(param1) {
var local1 = 'local1'
var local2 = 'local2'

function fn2(param2) {
var local2 = 'Inner local2'
console.log(local1)
console.log(local2)
}

function fn3(param3) {
var local2 = 'fn3 local2'
fn2(local2)
}
}

浏览器看到这一段代码时,会先将它变成抽象语法树。

global 和 fn1 同级

fn1 创建了五个变量,local1 local2 param1 fn2 fn3

深入阅读

3. Call Stack

数据结构 栈 – Stack 先进后出 后进先出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a()
b()
c()

funcntion a (){
...
}

funcntion b (){
...
}

funcntion c (){
...
}

4. this & arguments

  • this 是隐藏的第一个参数,且必须为对象
1
2
3
4
5
6
7
8
9
function f(){
console.log(this)
console.log(arguments)
}

f.call() // window
f.call( {name: 'xu'} ) // {name: 'xu '}, []
f.call( {name: 'xu'},1 ) // {name: 'xu'}, [1]
f.call( {name: 'xu'},1,2 ) // {name: 'xu'}, [1,2]

为什么我们使用 f.call() 而不是 f()?

因为 f() 就是 阉割版的 f.call()

  • this 为什么必须是对象

    因为 this 就是函数与对象之间的羁绊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var person = {
name: 'xu'
sayHi: function(person){
console.log('hi, im' + person.name)
}
sayBye: function(person){
console.log('bye, im' + person.name)
}
}

person.sayHi(person)
person.sayBye(person)

// 能不能变成
person.sayHi()
person.sayBye()

// 如果你不想吃语法糖
person.sayHi.call(person)
person.sayBye.cal(person)

// 新手疑惑的写法
var fn = person.sayHi
person.sayHi() // this === person
fn() // this === window

因为函数的本质是存储在堆内存中的一串地址数据,它本身只是一个数值,所以受上下文环境影响(函数可以在不同的上下文环境中运行)。

this 就是 call 的第一个参数!call 的其他参数统称为 arguments
https://zhuanlan.zhihu.com/p/23804247

5. call/apply

比如我们想要求一个数组之中所有数的总和,但是我们不知道这个数组有多少项目,那我们可以使用 apply。

当我们不确定数据的长度的时候,我们可以使用 apply 来代替 call。

6. bind

call 和 apply 是直接调用函数,而 bind 是返回一个函数,他没有调用原来的函数,这个新的函数会 call 原来的函数,call 的参数你来指定。

7. return

每个函数都有 return,如果不写那就是 undefined

8. 柯里化/高阶函数

返回函数的函数

  • 柯里化:将 f(x,y) 变成 f(x=1)(y) ,用来惰性求值,也就是将真实计算拖延到最后再做

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 柯里化之前
    function sum(x, y){
    return x + y;
    }

    // 柯里化之后
    function addOne(y){
    return sum(1, y);
    }

    // 柯里化之前
    function Handlebar( template, data ) {
    return template.replace('{{name}}', data.name)
    }

    // 柯里化之后
    function Handlebar ( template, data ) {
    return fucntion ( data ) {
    return template.replace('{{name}}', data.name)
    }
    }

    相关文章:http://www.yinwang.org/blog-cn/2013/04/02/currying
    https://zhuanlan.zhihu.com/p/31271179

  • 高阶函数

    在数学和计算机科学中,高阶函数是至少满足下列一个条件额函数:

    • 接受一个或多个函数作为输入:forEach map sort
    • 输出一个函数:loadsh.curry
    • 不过它通常同时满足两个条件:bind

image-20200512184722919

9. 回调 Callback

  • 名词形式:被当作参数的函数就是回调
  • 动词形式:调用这个回调

回调和异步没有任何关系

10. 构造函数

返回对象的函数就是构造函数

一般首字母大写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Number(1)
new String(2)
new Function()

// 我们来写一个构造函数
function Poor() {
return {}
}
var a = new Poor

// 如果不写 new 就会返回 undefined
function Poor() {

}
var a = Poor

不过很少有函数返回对象,大多都是基础数据类型。

11. 箭头函数

ES 带来的特别好用的一个东西

很简单:

1
2
3
4
5
6
7
8
// 正常的写一个函数
function fn(x, y){
return x + y
}

// 使用箭头函数
var fn = y => { return x+y }

箭头函数不存在 this(官方明示 this 不好用)

我们以 setTimeout 为例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
setTimeout(function(){
console.log(this)
}.bind({name:'xu'}), 1000)
// {name:'xu'}

setTimeout(function(){
console.log(this)
setTimeout(function(){
console.log(this)
}, 1000)
}.bind({name:'xu'}), 1000)
// window
// {name: 'xu'}

setTimeout(function(){
console.log(this)
setTimeout(function(){
console.log(this)
}.bind(this), 1000)
}.bind({name:'xu'}), 1000)
// {name:'xu'}
// {name:'xu'}

以上是旧的方式,我们需要使用到 bind

下面我们来看一下箭头函数的方法:

1
2
3
4
5
setTimeout(function(){
console.log(this)
setTimeout((a) => cosnole.log(this),1000 )
}.bind({name:'xu'}), 1000)
// 箭头函数是没有 this 的,它会往上找

如果你希望某个函数里面的 this 和外面的 this 是一个 this 的时候,你就可以使用箭头函数。

每次进入一个函数的时候,首先它会进入栈,然后一定会确定一个 this,没法不传,它是一个 JS 设计的漏洞。我们使用箭头函数来规避掉麻烦的 this,但是它是有 arguments 的。

这才是真正的函数。

下面我们来看一下奇怪的东西:

1
2
3
4
5
var fn = () => console.log(this)
// undefined

fn.call({name:'xu'})
// undefined

这样子 this 就从参数转变为变量。

这就回到了我们之前提到的词法作用域上面,当我们声明一个带参数的函数的时候,它会新建此参数。

习题

请写出一个柯里化其他函数的函数 curry,这个函数能够将-接受多个参数的函数-,转变为-多个接受一个参数的函数-,见示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// loadsh.curry
function curry(???){
???
return ???
}

var abc = function(a, b ,c){
return [a, b, c];
};

var curried = curry(abc);

curried(1)(2)(3);
// => [1, 2, 3]

curried(1, 2)(3);
// => [1, 2, 3]

curried(1, 2, 3);
// => [1, 2, 3]

写一个函数的时候,我们只要关注输入和输出。

形式大于逻辑:

1
2
3
4
5
6
7
8
9
10
11
function curry(func, fixedParams){
if( !Array.isArray(fixedParams) ) { fixedParams = [] }
return function(){
let newParams = Array.prototype.slice.call(arguments);
if( (fixedParams.length+newParams,length)<func.length ){
return curry(func, fixedParams.concat(newParams));
}else{
return func.apply(undefined, fixedParams.concat(newParams));
}
}
}
编辑