你真的懂函数嘛

214 阅读5分钟

1.定义

1.1 匿名函数

// 匿名函数,声明未引用会报错
function () {
    return 1
}
// fn引用了这个匿名函数,fn存的是地址,函数存在堆内存中
var fn = function (){
    return 1
}
// 把函数的地址复制给fn2,fn2现在指向这个函数
// 匿名函数也有名字
var fn2 = fn
fn.name // fn
fn2.name // fn

1.2 具名函数

// fn3是具名函数,作用域为all
function fn3() {
    return 1
}
// fn4作用域只是本身,不在顶级全局,只能通过fn5访问
var fn5 = function fn4() {
    return 1
}
console.log(fn3) // fn3(){}
console.log(fn4) // fn4 is not defined

1.3 箭头函数

  • 箭头函数不接受this指定(指定无效)
  • 箭头函数的this按照作用域向上找,自己没有this
  • 箭头函数无法call指定,只能向上找
// 单个参数,单句返回
var fn6 = i => i+1
fn6(7) // 8
fn6.name // fn6 箭头函数也有name
// 多个参数,函数体多句
var fn7 = (i, j) => {
    console.log(i, j);
    rentun i + j;
}

2.词法作用域(静态作用域)

  • 一个函数能访问哪些变量,在词法分析时已经确定,与执行顺序,调用顺序没有关系,与具体值无关(看下一个例子),只分析语义。
 var global1 = 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(){
         var local2 = 'fn3 local2'
         fn2(local2)
     }
 }

var a = 1
function b() {
    console.log(a)
    // 此时如果访问的a是全局的a,自己没有声明a
}
// 但是b未执行,不能确定a的具体值
a = 2;
b(); 
// 2, 访问的是全局的a,但是值需要具体执行时确定

3.call stack

function a(){
   console.log('a')
 return 'a'  
}
function b(){
   console.log('b')
   return 'b'
}
function c(){
   console.log('c')
   return 'c'
}
a()
b()
c()
// a()先入栈,执行a()
// console.log('a')入栈,执行,出栈,
// 执行return a,返回到a()的位置,a()c出栈
// b和c以此类推

4.this & arguments

  • fn()调用是阉割版的call,不考虑传入的this,浏览器用call调用,undefined会根据具体代码自动被填充
  • this语法糖存在的意义(让函数有可依托的对象)
  • this就是call的第一个参数call的其他参数统称为 arguments
  • this 是隐藏的第一个参数,且一般是对象(如果不是对象,就显得很没有意义了)
  function f(){
      console.log(this)
      console.log(arguments)
  }
  f.call() // window, []
  f.call({name:'frank'}) // {name: 'frank'}, []
  f.call({name:'frank'},1) // {name: 'frank'}, [1]
  f.call({name:'frank'},1,2) // {name: 'frank'}, [1,2]
  • this必须是对象,就算传入10,也会new Number(10)转换
  • this存在的意义如下例
     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.sayHi(person)
      person.sayBye(person)
      person.say(person, 'How are you')
      // 没有this需要把自己传入,繁琐

      // 想改造,能不能变成 
      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

5.call / apply

  • fn.call(asThis, p1,p2) 是函数的正常调用方式,call的参数是一个个的
  • 当你不确定参数的个数时,使用apply,params传入数组,不确定个数的arguments可以直接遍历
  • fn.apply(asThis, params) // params是数组

6.bind

  • call 和 apply 是直接调用函数,而 bind 则是返回一个新函数(并没有调用原来的函数),这个新函数会 call 原来的函数,call 的参数由自己指定。
this.onClick.bind(this) // bind: 创建一个新函数,call了指定的this

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

7.return

  • 每个函数都有 return
  • 如果你不写 return,就相当于写了 return undefined

8.柯里化/高阶函数

8.1 柯里化

  • 柯里化:固定一个参数,将 f(x,y) 变成 f(x=1)(y) 或 f(y=1)x
    • 把一个函数的一个参数固定下来,得到一个新的函数
//柯里化之前
  function sum(x,y){
      return x+y
  }
  //柯里化之后
  function addOne(y){
      return sum(1, y)
  }
  //早期模板引擎,给一个模板,一个数据,返回一个html
  //柯里化之前
  function Handlebar(template, data){
      return template.replace('{{name}}', data.name)  
  }
  //柯里化之后
  function Handlebar2(template){
      return function(data){
          return template.replace('{{name}}', data.name)
      }
  }
// 柯里化之前 调用
// 改掉模板里的name 需要很多字符串 只想写一次
Handlebar('<h1>Hi,I am {{name}}</h1>', {name: 'emmm'})
Handlebar('<h1>Hi,I am {{name}}</h1>', {name: 'dododo'})
var template = '<h1>Hi,I am {{name}}</h1>';
Handlebar(template, {name: 'checkyo'});
// 柯里化之后 调用 先接收一个返回函数
// 可以用来做 惰性求值
// 调用第一个时候,什么都没做,使用时候生效,第二次调用时候
var t = Handlebar2('<h1>Hi,I am {{name}}</h1>');
t({name: 'freestyle'})

8.2 高阶函数

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

      1. 接受一个或多个函数作为输入:forEach sort map filter reduce
    array.sort(function(a,b){a-b})
    array.forEach(function(a){})
    
      1. 输出一个函数:eg: lodash.curry
    fn.bind.call(fn, {}, 1,2,3) //fn this 参数
    
      1. 不过它也可以同时满足两个条件,接收函数以及返回函数:Function.prototype.bind
  • 高阶函数,接收一个函数,返回新的函数,作用于函数的组合

9.回调

  • 名词形式:被当做参数的函数就是回调
  • 动词形式:调用这个回调
// 传过去,调用一下
// 同步回调
array.forEach(function(a){})
  • 注意回调跟异步没有任何关系,同步回调和异步回调
// 异步回调
setTimeout(fn, 1000)

10.构造函数

function Empty() {
    {...}
}
var empty = new Empty // new 会自动执行return 语法糖