JS之函数2

重要概念


  1. 定义

    • 匿名函数
    • 具名函数
    • 箭头函数
  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)
         }
     }
    
  3. call back

  4. this & arguments

    • 重要:this 就是 call 的第一个参数!call 的其他参数统称为 arguments。如果你记住了这个规则,那么网上其他关于 this 的解释在此时都变得很啰嗦了。
    • 如果传进去的不是对象,而是一个数字1,那么会被变成对象,如new Number(1)
    • 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 为什么必须是对象
      因为 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')
      
      // 能不能变成 
      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) 是函数的正常调用方式
    • 当你不确定参数的个数时,就使用 apply
    • fn.apply(asThis, params)
  6. bind
    call 和 apply 是直接调用函数,而 bind 则是返回一个新函数(并没有调用原来的函数),这个新函数会 call 原来的函数,call 的参数由你指定

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

  8. 柯里化 / 高阶函数
    返回函数的函数

    • 柯里化:将 f(x,y) 变成 f(x=1)(y) 或 f(y=1)
        //柯里化之前
        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
        • 输出一个函数:lodash.curry
        • 不过它也可以同时满足两个条件:Function.prototype.bind
  9. 回调
    名词形式:被当做参数的函数就是回调
    动词形式:调用这个回调
    注意回调跟异步没有任何关系

  10. 构造函数
    返回对象的函数就是构造函数
    一般首字母大写

课后题答案:

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));
        }
    };

函数声明


  1. 匿名函数
    • 一般直接声明匿名函数会报错的,我们将匿名函数赋给一个变量,这个变量记录的是堆内存中的地址,函数体是记录在堆内存中
    • 匿名函数的名称跟赋给的变量名称相同,即便是匿名函数,但是有name属性
    • image.png
  2. 具名函数
    • 具名函数是具有名字的函数,这个名字其实是一个变量,这个变量具有作用域
    • 当我们直接写一个具名函数fn3,那么在整个外围的作用域里面都能访问fn3这个变量
    • 当我们将一个具名函数在fn4赋值给一个变量fn5,那么fn4其实只能在函数作用域中访问(即高亮部分),函数作用域之外就无法访问,但是这个变量fn5就可以在外层访问
    • image.png
  3. 箭头函数
    • 箭头函数一个参数不用使用括号包起来,多个参数需要使用括号包起来
    • 箭头函数语法块只有一句好不需要用大括号包起来,不需要写return,build自动将语句执行的结构返回;但是多个语句需要用大括号包起来,同时将return的值表明。

词法作用域


  1. 当浏览器看到下面这个语法的时候,不会马上执行它,而是会去做一个叫抽象词法树的东西。将下面的代码变成一棵树状的结构,以便于后续的执行
    var global1 = 1
    function fn1(param1){
        var local1 = 'local1'
        var local2 = 'local2'
        function fn2(params){
            var local2 = 'inner local2'
            console.log(local1)
            console.log(local2)
        }
        function fn3(){
            var local2 = 'fn3 local2'
            fn2(local2)
        }
    }
    
  2. 函数中传入的形参也是一种变量声明,找寻变量是根据词法树来确定的,本级找不到的话,就往上一级去找。一个函数里面能访问哪些变量,在做词法分析的时候就已经确定了。此法分析只分析语义,即到底能访问的是哪个变量等,不分析具体的值


    image.png

Call Stack


  1. Stack就是栈,即先进后出。跟队列正好相反。
  2. 浏览器拿到代码的第一步是做词法解析,会将所有声明操作进行前置。
  3. 推荐一个网站,可以查看Stack的运行方式:http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4=
  4. 每一次都会记录进入的位置,一个位置里面可以有其他位置进入。从一个位置进入,就还从这个位置出来,这就形成了先进后出。
  5. image.png
  6. 递归就是这种无限进入下一个位置,我们举个例子,菲薄拉起数列,每一项是前面两项的和。


    image.png
  7. 总结call Stack:
    • JS是单线程,在执行一长串代码的时候,会将当前环境都记住,比如能访问哪些变量等。
    • 突然看到一个函数,这个时候就切换环境,因为它要进入这个函数,因为函数的代码并不在原先的环境中,
    • 为了记住要回到原先的环境中,就做一个记录,可能会有很多层的记号,将这些记号放入栈里面,叫做调用栈,只要多一层调用栈,就出现一个记录,进入新的函数进行执行
    • 如果函数里面还有函数,就继续进入。每次回来,就先回最后进入的那个标记
  8. call Stack是一个理论上的知识,可以帮助我们理解函数的调用。

this & arguments


  1. 这个跟调用栈具有很大的关系,在进入一个函数的时候,除了需要记录下进入的地址,还得记录下传给函数的参数有哪些。
  2. call的第一个参数就是this,剩下的参数就是arguments,不传就是空数组
  3. 为什么要使用call,而不是f(),因为f()是阉割版的call。当使用f(1)的时候,1不被当做this,这是因为浏览器会猜测this是window,将1作为参数。而使用call则是将函数的this指明了,所以不要使用f()


    image.png
  4. 传进去一个10,就会将10转成number对象。this是函数与对象之间的羁绊。


    image.png
  5. person.sayHi()与person.sayHi.call(person)是等价的,前者会通过词法解析变成后面这样的。两者的意思都是以person为this调用sayHi。
  6. JS之父为了让我们person.sayHi()能被解析,就发明了this。写了点就将点前面的当做this,没有写点,就将windows作为this


    image.png
  7. this就是让函数有一个可依托的对象,使用call一定要将第一个参数传进去,如果不依赖任何this,就可以传入undefined/null,否则可能会报错


    image.png
  8. 有种情况没法使用call来写,比如求和所有参数。无法确定参数的个数。可以使用循环遍历。但是使用apply更加方便,apply的第二个参数就是arguments,是一个数组,数组中有多少项都是可以的


    image.png

bind


  1. call和apply都是直接调用函数,而bind则是没有调用函数,而是返回一个新的函数
  2. onclick后面会绑定一个函数,这个函数的this会在浏览器的源代码中写好。我们一眼看是看不出来this是什么的,因为this等价于call的第一个参数


    image.png
  3. 我们有一个对象叫view,对象中有一个元素是element,有一个bindEvents的方法,这个方法所在的大环境值view,所以this就是view;但是里面onclick事件的this其实是被点击的元素,即触发事件的元素我们绑定事件的时候,需要将this指定为view.element,为了明确指定,我们使用_this拿到外层的this。使用onclick函数不能直接用,因为我们需要给其创造一个this。我们不能直接写this.element.onclick = this.onClick,因为后面的this是onclick函数指定的点击对象,可能是div等,但他们不具有onClick函数。我们需要使用一个函数包起来,去更改一下this,找到对应的onClick函数
    var view = {
        element: $('#div1'),
        bindEvents: function(){
            var _this = this
            this.element.onclick = function(){
                _this.onClick.call(_this)
            }
        },
        onClick: function(){
            alert('你点击了我!')
        }
    }
    
  4. 既然是指定的,为何不使用view代替this呢,这样不是更加明确吗,省得this不明确带来的各种麻烦
    var view = {
        element: $('#div1'),
        bindEvents: function(){
            view.onClick = function(){
                view.onclick
            }
        },
        onClick: function(){
            alert('你点击了我!')
        }
    }
    
  5. 我们可以使用bind做同样的事情,即更改onclick函数指定的this到想要的地方去。这样写法的意思就是.bind会返回一个新的函数,这个函数会将bind前面的函数(这边就是this.onClick)包起来,bind的作用就是往onClick后面加一个call
    var view = {
        element: $('#div1'),
        bindEvents: function(){
            this.element.onclick = this.onClick.bind(this)
        },
        onClick: function(){
            alert('你点击了我!')
        }
    }
    
    image.png
  6. 上面this.onClick.bind(this)其实就是实现了
    function(){
    this.onClick.call(this)
    }
    而且this.onClick.bind(this)的this没有进入一个新的函数,所以我们不需要声明一下下划线_this,bind里面是什么,就相当于函数里面的call里面是什么,而现在的使用bind(this)跟外面的this是一样的,所以不需要使用下划线this
  7. 这样,我们就达到这个目的了,当我们调用onclick函数的时候,会去调用onClick函数,调用的方式就是在onClick后面加一个call,bind的作用就是往onclick后面加一个call,但是不是现在加,而是在调用bind后的新函数的时候加
  8. bind返回的是一个函数,这个函数的作用就是调用之前的函数,之前的函数后面会接一个apply或者call,里面的参数就是你写给bind的参数
    var view = {
        element: $('#div1'),
        bindEvents: function(){
            this.onClick.bind = function(x,y,z){
                var oldFn = this  // 这个this就是外面的this.onClick
                return function(){
                    oldFn.call(x,y,z)
                }
            }
            this.element.onclick = this.onClick.bind(this)
        },
        onClick: function(){
            this.element.addClass('active')
        }
    }
    

柯里化


  1. 柯里化就是将多个参数中一部分固定下来,变成一种偏函数,得到的一种新函数


    image.png
  2. 意义:柯里化在模板引擎中的使用是可以等到数据全部齐全之后再进行渲染
    image.png
  3. 可以用来做惰性求值:我们在调第一个函数的时候其实啥也没做,我们在真正使用的时候再去生效
  4. 用的不是很多的

高阶函数


  1. 接收函数作为参数或者返回的是函数,那么就是高阶函数
  2. 接收函数
    • array.sort(function(a, b){a-b})
    • array.forEach(function(a){})
    • array.map(function()}{})
    • array.filter(function(){})
    • array.reduce(function(){})
  3. 返回函数
    • fn.bind.call(fn, {}, 1,2,3):bind是接收一个函数fn的,这个fn会被bind包装成另一个新的函数,这个新的函数会接受一些参数,bind的call的第二个参数,会作为bind的call的第一个参数fn的call的第一个参数,即其this,后面的1,2,3就是fn剩余的参数。
  4. 意义:将函数进行任意组合,这个在react组件里面用的很多
  5. 例子:求和所有偶数的和,几种写法不一样,使用高阶函数也是很直观的,还简洁


    image.png

回调和构造函数

  1. callback,函数作为参数传入函数中,并调用了
  2. 回调跟异步没有任何关系
  3. 返回对象的函数就是构造函数,构造函数中可以不return,因为会默认返回对象的,因为我们会使用new来调用构造函数


    image.png
  4. 函数很少返回对象

箭头函数


  1. 箭头函数没有this,箭头函数里面的this就是外面的this


    image.png
  2. 箭头函数是无法指定this的


    image.png

牛刀小试

  1. 请写出一个柯里化其他函数的参数,这个函数能够将接受多个参数的函数变成多个接受一个参数的函数。
    • 我们在写函数的时候需要弄清楚两个事情,一个是输入,一个是输出,弄清楚之后再写中间的逻辑
    • 一种最为简单的实现方式
      function sum(x, y){
          return x + y
      }
      function curry(fn, p1){
          return function(p2){
              return fn.call(undefined, p1, p2)
          }
      }
      var addOne = curry(sum, 1)
      addOne(2)  // 3
      addOne(3)  // 4
      
    • 上面的写法不满足fn(1)(2)这样子,怎么改?
      function sum(x, y){
          return x + y
      }
      function curry(fn){
          return function(p1){
              return function(p2){
                  return fn.call(undefined, p1, p2)
              }
          }
      }
      curry(fn)(1)(2)
      
    • 只有将买所有的值都传到位了,才会最终返回一个值
    • 如果我们不知道接受的参数个数呢?
    • 函数的length属性就反映着传入函数的参数个数
    • image.png
      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));
              }
          };
      }
      
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • JavaScript 介绍js的基本数据类型。 Undefined、Null、Boolean、Number、St...
    cuikangjie阅读 468评论 0 3
  • javascript的组成 javascript 由以下三部分组成: ECMAscript(核心):javascr...
    这是这时阅读 931评论 0 3
  • 函数定义 匿名函数函数声明的时候 不要给名字 把它付给一个变量 作为函数的引用变量里面存的是 函数的地址函数是个对...
    xingkong_s阅读 226评论 0 0
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,421评论 1 45
  • JavaScript ECMAScript(ES):规定了一些基础核心的知识(变量、数据类型、语法规范、操作语句等...
    小K强阅读 439评论 0 0