美文网首页饥人谷技术博客
你真的懂函数吗?

你真的懂函数吗?

作者: 饥人谷_Jack | 来源:发表于2018-03-12 06:30 被阅读13次

    函数声明方式

    匿名函数

    function后面直接跟括号,中间没有函数名的就是匿名函数。

    let fn = function() {
        console.log('我是fn')
    }
    let fn2 = fn
    console.log(fn.name) //fn
    console.log(fn2.name)//fn,fn和fn2指向的是同一个function。
    

    具名函数

    function后面有函数名字的,不是直接跟括号的的就是具名函数。
    如果把一个具名函数赋值给一个变量,那么这个具名函数的作用域就不是window了。

    let fn = function fn1() {
      console.log('function')
    }
    console.log(fn.name) //fn1
    console.log(fn1,name) // ReferenceError: fn1 is not defined
    

    箭头函数

    箭头函数是es6知识点,具有以下几个特点:

    1. 如果只有一个参数,可以省略小括号。
    2. 如果有至少有两个参数,必须加小括号。
    3. 如果函数体只有一句话可以省略花括号,并且这一句作为返回值return。
    4. 如果函数体至少有两句必须加上花括号。
    5. 箭头函数里面是没有this的。
    let fn = e => e+1
    console.log(fn(1)) //2
    
    let fn1 = (i,y) => i+y
    console.log(fn1(2,3)) //5
    
    let fn2 = (i,y) => {
      i+=1;
      y+=2;
      return i+y
    }
    console.log(fn2(5,6)) //13
    

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

    静态作用域又叫做词法作用域,采用词法作用域的变量叫词法变量。词法变量有一个在编译时静态确定的作用域。词法变量的作用域可以是一个函数或一段代码,该变量在这段代码区域内可见(visibility);在这段区域以外该变量不可见(或无法访问)。词法作用域里,取变量的值时,会检查函数定义时的文本环境,捕捉函数定义时对该变量的绑定。

    词法作用域:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。 with和eval除外,所以只能说JS的作用域机制非常接近词法作用域(Lexical scope)。

    通过词法作用域树能判断变量指向关系,但是不能断定变量的值,变量的值还需要根据执行顺序进一步作出判断,看一下例子:

    因为JavaScript采用的是词法作用域,bian'liang的作用域基于函数创建的位置,跟调用时的位置无关。

    var i = 1,
        j = 2,
        k = 3;
    
    function a(o, p, x, q) {
        var x = 4;
        alert(i);
    
        function b(r, s) {
            var i = 11,
                y = 5;
            alert(i);
    
            function c(t) {
                var z = 6;
                alert(i);
            };
            
            var d = function() {
                alert(y);
            };
            c(60);
            d();
        };
        b(40, 50);
    }
    a(10, 20, 30); //1 11 11 5
    
    /**
    * 模拟建立一棵语法分析树,存储function内的变量和方法
    */
    var SyntaxTree = {
            // 全局对象在语法分析树中的表示
        window: {
            variables:{
                i:{ value:1},
                j:{ value:2},
                k:{ value:3}
            },
            functions:{
                a: this.a
            }
        },
    
        a:{
            variables:{
                x:'undefined'
            },
            functions:{
                b: this.b
            },
            scope: this.window
        },
    
        b:{
            variables:{
                     i:'undefined'
                y:'undefined'
            },
            functions:{
                c: this.c,
                d: this.d
            },
            scope: this.a
        },
    
        c:{
            variables:{
                z:'undefined'
            },
            functions:{},
            scope: this.b
        },
    
        d:{
            variables:{},
            functions:{},
            scope: {
               scope: this.b
            }
        }
    };
    
    /**
    * 活动对象:函数执行时创建的活动对象列表
    */
    let ActiveObject = {
        window: {
            variables: {
                i: { value: 1 }
                j: { value: 2 }
                k: { value: 3 }
            },
            functions: {
                    a: this.a
            }
        }
    
        a: {
            variables: {
                x: { vale: 4 },
                functions: {
                    b: this.b
                },
                scope: this.window,
                params: {
                    o: { value: 10 },
                    p: { value: 20 },
                    x: this.variables.x
                    q: { vale: 'undefined' }
                },
                arguments: [this.params.o, this.params.p, this.params.x]
            }
        }
    
        b: {
            variables: {
                i: { vale: 11 },
                y: { vale: 5 },
            },
            functions: {
                c: this.c,
                d: this.d
            },
            params: {
                r: { value: 40 }
                s: { value: 50 }
            },
            arguments: [this.params.r, this.params.scope]
            scope: this.a
        }
    
    
        c: {
            variables: {
                z: { value: 6 },
                functions: {},
                params: {
                    t: { value: 60 }
                },
                arguments: [this.params.t]
                scope: this.b
            }
        }
    
        d: {
            variables: {},
            functions: {},
            params: {},
            arguments: []
            this.scope: this.b
        }
    
    }
    

    call stack

    进入call stack 时的一些规则:

    1. 函数的所有形参(如果我们是在函数执行上下文中)

      • 由名称和对应值组成的一个变量对象的属性被创建;没有传递对应参数的话,那么由名称和 undefined 值组成的一种变量对象的属性也将被创建。
    2. 所有函数声明(FunctionDeclaration, FD)

      • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建;如果变量对象已经存在相同名称的属性,则完全替换这个属性。
    3. 所有变量声明(var, VariableDeclaration)

      • 由名称和对应值(undefined)组成一个变量对象的属性被创建;如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
    /*
    * example1:形参
    */
    function test(a, b) {
        /*
        var a = 10
        var b = undefined
        根据规则1,在进入执行上下文时会自动对形参声明并且赋值。
        */ 
      console.log(a)
      var c = 10;
      function d() {}
      var e = function _e() {};
      (function x() {});
    }
    test(10); // 10
    
    /*
    * example2:函数声明
    */
    function test(a, b) {
      console.log(a)
      function a() {}
      var e = function _e() {};
    }
    test(10); // ƒ a() {} .根据规则2,进入执行上下文会自动声明形参并且赋值,但是同名的函数声明会替换这个变量。
    
    function test(a, b) {
      console.log(a)
      var a = 30;
      var a = function _e() {};
    }
    test(10); // 10 .根据规则2,进入执行上下文会自动声明形参并且赋值,但是同名的函数声明会替换这个变量。
    
    /*
    * example3:变量声明
    */
    console.log(foo);//会打印出foo函数,根据规则3,同名的变量声明不会干扰函数声明和形参
    
     function foo(){
        console.log("foo");
    }
    
    var foo = 1;
    

    this和arguments

    函数调用

    在es5中,函数有四种调用方式:

    1. fn(p1,p2)
    2. obj.fn(p1,p2)
    3. fn.call(context,p1,p2)
    4. fn.apply(context,p1,p2)
    

    第三和第四种才是正常的js函数调用方式,其他两种就是语法糖。

    fn(p1,p2)     等价于 fn.call(undefined,p1,p2) 等价于 fn.apply(context,[p1,p2])
    obj.fn(p1,p2) 等价于 obj.fn.call(obj,p1,p2)   等价于 obj.fn.apply(obj,[p1,p2])
    

    如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)

    this是什么??

    this是call的第一个参数!!!!

    var obj = {
      foo: function(){
        console.log(this)
      }
    }
    
    var bar = obj.foo
    obj.foo() // 打印出的 this 是 obj
    bar() // 打印出的 this 是 window
    
    obj.foo() 相当于 obj.foo.call(obj) 也就相当于把函数名前面的作为call的第一个参数,也就是this,如果没有就是window。
    bar() 相当于 bar.call(undefined) 
    

    在执行函数的时候,this是隐藏的一个参数,且必须是一个对象,如果不是,js是自动把它转为对象。

    function fn() {
        console.log(this)
        console.log(arguments)
    }
    fn.call(1,2,3) // Number {1}  [2,3]
    

    arguments

    arguments是伪数组它类似于Array,但除了length属性和索引元素之外没有任何Array属性。
    call和apply里面除了第一个参数之外的都是arguments,如果arguments的个数少建议使用call,使用apply也可以,如果不确定就使用apply。
    使用一下方法吧arguments转为真正的数组:

    var args = Array.prototype.slice.call(arguments);
    var args = [].slice.call(arguments);
    
    // ES2015
    const args = Array.from(arguments);
    const args = [...arguments]
    

    bind

    MDN 官方文档对 bind() 的定义:

    The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

    大概意思就是,bind会返回一个新的函数(并没有的调用原来的函数),这个新函数会call原来的函数,call的参数由你决定。看例子:

            this.x = 9;
            var module = {
              x: 81,
              getX: function() { return this.x; }
            };
            
            var retrieveX = module.getX;
            var boundGetX = retrieveX.bind(module);
            boundGetX(); // 81
    

    retrieveX.bind(module)返回了一个新的函数boundGetX,然后调用这个新的函数的时候,把这个函数里面的this绑定到了module对象上,所以this.x就相当于module.x也就是等于81.

    柯里化

    在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的,尽管它是Moses Schönfinkel和戈特洛布·弗雷格发明的。

    说的明白一点就是,给函数传递一部分参数,让它返回一个函数去处理其他参数,举个例子,求三个数之和:

    let addOne = function add(x) {
      return function(y) {
        return function(z) {
          return x+y+z
        }
      }
    }
    
    let one = addOne(3)
    console.log(one)//ƒ (y) {return function (z) {return x + y + z}}
    let two = one(4)
    console.log(two)//ƒ (z) {return x + y + z}
    let three = two(5)
    console.log(three)//12
    

    javascript函数柯里化--详细说明链接

    高阶函数

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

    1. 接受一个或多个函数作为输入
    2. 输出一个函数

    举一些高阶函数的例子:

    /*
    *接受一个或多个函数作为输入
    */
    1. Array.prototype.filter()
    2. Array.prototype.forEach()
    3. Array.prototype.reduce()
    4. Array.prototype.map()
    5. Array.prototype.find()
    6. Array.prototype.every()
    
    /*
    *输出一个函数
    */
    1. fn.bind(args)
    

    回调函数

    函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

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

    回调函数的使用场合

    1. 资源加载:动态加载js文件后执行回调,加载iframe后执行回调,ajax操作回调,图片加载完成执行回调,AJAX等等。
    1. DOM事件及Node.js事件基于回调机制(Node.js回调可能会出现多层回调嵌套的问题)。
    1. setTimeout的延迟时间为0,这个hack经常被用到,settimeout调用的函数其实就是一个callback的体现
    1. 链式调用:链式调用的时候,在赋值器(setter)方法中(或者本身没有返回值的方法中)很容易实现链式调用,而取值器(getter)相对来说不好实现链式调用,因为你需要取值器返回你需要的数据而不是this指针,如果要实现链式方法,可以用回调函数来实现。
    1. setTimeout、setInterval的函数调用得到其返回值。由于两个函数都是异步的,即:他们的调用时序和程序的主流程是相对独立的,所以没有办法在主体里面等待它们的返回值,它们被打开的时候程序也不会停下来等待,否则也就失去了setTimeout及setInterval的意义了,所以用return已经没有意义,只能使用callback。callback的意义在于将timer执行的结果通知给代理函数进行及时处理。

    回调函数的传递

    传递的方式有两种,函数引用和函数表达式。

    $.get('myhtmlpage.html', myCallBack);//这是对的
    $.get('myhtmlpage.html', myCallBack('foo', 'bar'));//这是错的,那么要带参数呢?
    $.get('myhtmlpage.html', function(){//带参数的使用函数表达式
    myCallBack('foo', 'bar');
    });
    

    箭头函数与es5的函数主要区别

    箭头函数的主要区别在this,箭头函数是没有this这个概念的,看例子:

    setTimeout(function(a){
      console.log(this) //这个this指的是{name:'Jack'}
      setTimeout(function(a){
        console.log(this) //这个this指的是window,因为没有bind,调用setTimeout的是window
      },1000)
    }.bind({name:'Jack'}),1000)
    
    setTimeout(function(a){
      console.log(this) //这个this指的是{name:'Jack'}
      setTimeout(function(a){
        console.log(this) //这个this指的是{name: "Jack"},因为bind了外面的this也就是{name: "Jack"}
      },1000)
    }.bind({name:'Jack'}),1000)
    
    setTimeout(function(a){
      console.log(this) //这个this指的是{name:'Jack'}
      setTimeout(a=>console.log(this),1000)//这个this指的是{name:'Jack'},因为箭头函数没有this的概念,它指的this就是外面的this,也就是{name:'Jack'}
    }.bind({name:'Jack'}),1000)
    

    至此基本上说了js的所有函数内容,只是简单举个例子,更深入的研究还需要看一些其他大佬的博客哦~~~~

    相关文章

      网友评论

        本文标题:你真的懂函数吗?

        本文链接:https://www.haomeiwen.com/subject/tubgfftx.html