你真的懂函数吗

函数声明

匿名函数 就是声明的时候不给名字

function(){ return 1 ;} 你声明了它 但是又不能引用到它

1
2
3
4
5
6
7
8
// function(){ return 1 ;}  匿名函数 相当于废话    会报错   你只有给了引用才能使用
//fn 记录的是函数的地址 ==>引用
var fn = function(){
return 1;
}
console.log(fn.name) //fn
var fn2 = fn; //这里不是把function(){ return 1 ;} 复制给fn2 而是把地址复制给fn2
console.log(fn2.name) //fn

具名函数

1
2
3
4
function fn3(){
return 1;
}
console.log(fn3); //可以访问 fn3它是一个变量 作用域是整个区域

将具名函数赋值给一个变量 它的作用域就变了

1
2
3
4
5
var fn4 = function fn5(){return 1;}
console.log(fn4.name) //fn5
// 这个具名函数的作用域就是他函数本身那部分 外部访问不到
// console.log(fn5) //fn5 is not defined
// console.log(fn5.name) //报错

全局作用域的函数 赋值给变量他的作用域不受影响

1
2
3
4
5
function fn6(){return 1;}
var fn7 = fn6;
console.log(fn6.name) //fn6
console.log(fn7.name) //fn6
console.log(fn7) //function fn6(){return 1;}

箭头函数

1
2
3
4
5
6
7
8
9
var fn8 = ()=>1; //无参数 返回1
var fn9 = i=>i+1; //有参数返回 参数+1;
var fn10 =(i,j)=>{
console.log(i)
console.log(j)
return i+j
} //有参数 并且函数内部有多条语句
console.log(fn8.name) //fn8
// 箭头函数和 匿名函数 具名函数唯一的区别就是 this(口诀:箭头函数内外this不变)

fn1 调用的时候不会立即执行
而是会生成一个抽象语法树
校验里面的每句话 如果有错误就停止执行
没错再从头开始执行

词法作用域

一个函数能访问那些变量 在做词法分析的时候就确定了

词法树分析的是语义
只能确认a就是这个函数内部的a
不能确定的是“值”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var global = 1;
function fn1(param1){
var local1 = 'local1';
var local2 = 'local2';
function fn2(param2){
var local2 = 'fn2 local2';
console.log(local1);
console.log(local2);
}

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

经典面试题

1
2
3
4
var a = 1;
function b(){
console.log(a) // a一定是1吗?
}

你在看看

1
2
3
4
5
6
7
var a =1;
function b(){
console.log(a);
}
...若干代码后 突然一句
a = 2;
b(); //打出来的一定是1吗?

词法作用域只能确定这个a是不是那个a,不能确定a的值是不是那个值

call stack 调用栈

以后再更新。。。(没人考)

this & arguments

  • call的第一个参数就是this
  • call的第的哥参数后面的就是arguments(伪数组)
  • fn() 等同于 fn.call()
  • 非严格模式下,fn()和fn.call() this会转变为window
  • fn()是阉割版的fn.call()
1
2
3
4
5
6
7
8
function f(){
console.log(this)
console.log(arguments)
}
f.call() // window
f.call({name:'aaa'}) // {name: 'aaa'}, []
f.call({name:'aaa'},1) // {name: 'aaa'}, [1]
f.call({name:'aaa'},1,2) // {name: 'aaa'}, [1,2]

尽可能避免使用fn()的调用形式,因为会让你产生不知道this是谁的恐慌

  • this 为什么必须是对象
    因为 this 就是函数与对象之间的羁绊
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 第一步思考
var person = {
name: 'frank',
sayHi: function(person){
console.log('Hi, I am' + person.name)
},
sayBye: function(person){
console.log('Bye, I am' + person.name)
},
say: function(person, word){
console.log(word + ', I am' + person.name)
}
}

//这样写法很恶心每次都要把person传进去
person.sayHi(person)
person.sayBye(person)
person.say(person, 'How are you')
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
26
27
28
29
30
31
32
33
34
35
// 第二步思考
// 能不能变成这样调用
person.sayHi()
person.sayBye()
person.say('How are you')

// 那么源代码就要改了
var person = {
name: 'frank',
sayHi: function(){
console.log('Hi, I am' + this.name)
},
sayBye: function(){
console.log('Bye, I am' + this.name)
},
say: function(word){
console.log(word + ', I am' + this.name)
}
}


// 如果你不想吃语法糖
person.sayHi.call(person)
person.sayBye.call(person)
person.say.call(person, 'How are you')

// 还是回到那句话:this 是 call 的第一个参数
// this 是参数,所以,只有在调用的时候才能确定
person.sayHi.call({name:'haha'}) // 这时 sayHi 里面的 this 就不是 person 了
// this 真的很不靠谱

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

person.sayHi() 等价于 person.sayHi(person)
fn() 等价于 fn.call()

call / apply

  • 第一原则call的第一个参数是this
  • apply是另一版本的call

apply的使用场景就是在你不知道要传递的参数有多少个的情况

1
2
3
fn.call(asThis, p1,p2) 是函数的正常调用方式
当你不确定参数的个数时,就使用 apply
fn.apply(asThis, params)

bind

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

待更新。。。。。

柯里化

返回函数的函数

  • 柯里化:将 f(x,y) 变成 f(x=1)(y) 或 f(y=1)x

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //柯里化之前
    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){
    return function(data){
    return template.replace('{{name}}', data.name)
    }
    }

    柯里化可以将真实计算拖延到最后再做
    关于柯里化的高级文章:

柯里化唯一的好处就是惰性求值

高阶函数:

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
接受一个或多个函数作为输入:forEach sort map filter reduce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//接收一个函数

[3,1,2].sort(function(a,b){return a-b}) [3,1,2].sort.call([3,1,2],fn)
[1,2,3].forEach(function(val,idx,arr){}) [3,1,2].forEach.call([3,1,2],fn)
[1,2,3].map(function(val,idx,arr){}) [3,1,2].map.call([3,1,2],fn)
[1,2,3].filter(function(val,idx,arr){}) [3,1,2].filter.call([3,1,2],fn)
[1,2,3].reduce(function(){}) [3,1,2].reduce.call([3,1,2],fn)

//输出一个函数
function sum(a,b){return a+b}
function addOne(b){
return sum(1,b)
}

//输入一个函数,输出一个函数
fn.bind.call(fn,{},1,2,3);
  • 输出一个函数:lodash.curry
  • 不过它也可以同时满足两个条件:
  • Function.prototype.bind

高阶函数的好处就是任意组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var arr = [1,2,3,4,5,6,7,8];
var sum = 0;
//求偶数和
for(var i=0;i<arr.length;i++){
if(arr[i]%2===0){
sum+=arr[i]
}
}

// 高阶写法1
arr.filter(function(n){
return n%2===0
}).reduce(function(prev,next){
return prev+next
},0)

//高阶写法2
reduce(filter(arr,function(n){return n%2}),function(prev,next){return prev+next},0)
sort(filter(arr,function(n){return n%2===1}),function(a,b){return a-b})

回调函数

  • 名词形式:被当做参数的函数就是回调
  • 动词形式:调用这个回调
  • 注意回调跟异步没有任何关系

构造函数

  • 返回对象的函数就是构造函数
  • 一般首字母大写
1
2
3
4
5
6
7
8
9
10
11
12
var n = new Number(1)
var bool = new Boolean(true)
var s = new String('xxx');
var obj = new Object();


function Animal(){
this.name = '动物'
//return this; // 自动帮你返回一个对象
}
// 只要你new一个函数 就会帮你返回一个对象
var animal = new Animal(); //Animal.call({})

箭头函数

1
2
3
4
5
6
7
8
9
10
function fn(x,y){
return x+y;
}

var fn = (x,y)=>x+y
var fn = (x,y)=>{
//多句话要执行的时候
console.log(x)
return x+y
}

箭头函数没有this

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

setTimeout(function(){
console.log(this)
setTimeout(function(){
console.log(this)
},1000)
}.bind({name:'aaa'}),1000)
//第一秒 {name:'aaa'}
//第二秒 window 第二个延时器相当于 fn.call()
//call里没传递this就是 window

//如果想两次都打印 {name:'aaa'}就要这样
setTimeout(function(){
console.log(this)
setTimeout(function(){
console.log(this)
}.bind(this),1000)
}.bind({name:'aaa'}),1000)
//第二个延时器里的bind(this) this就是 {name:'aaa'}

箭头函数干掉了this

1
2
3
4
5
6
setTimeout(function(){
console.log(this)
setTimeout(()=>{
console.log(this)
},1000)
}.bind({name:'aaa'}),1000)

js的漏洞 function(){} 等价于 function(){}.call()
call的第一个参数就是this
call不传递参数就是window

强迫症就是要给箭头函数加this?

答案是不行 会被忽略

1
2
3
4
var fn =()=>{console.log(this)}
fn.call({name:'aaa'})

//打印window