犀牛书笔记(第8章 函数)

犀牛书笔记(第8章 函数)

定义函数

函数声明;函数表达式;箭头函数

函数声明:函数的名字变成了一个变量,这个变量的值就是函数本身。声明语句会被提升到包含脚本、函数或代码块的顶部,因此调用时,调用代码可以出现在函数定义代码之前。ES6以前函数声明只能出现在文件/其他函数的顶部。在ES6的严格模式下,函数声明可以出现在语句块中,对块的外部不可见。

函数表达式:可以没有函数名。最佳实践是使用const把函数表达式赋值给常量,以防止意外又给它赋新值而重新函数。如果函数表达式包含名字,则该函数的局部作用域中也会包含一个改名字与函数对象的绑定。函数名成了函数体内的一个局部变量。函数表达式不能再定义之前调用。

箭头函数:如果箭头函数的函数体是一个return语句,但是要返回的表达式是对象字面量,那必须把这个对象字面量放在一对圆括号里,避免分不清花括号代表函数体还是对象字面量。

嵌套函数:闭包

调用函数

函数调用:函数通过调用表达式被作为函数或方法调用。调用表达式包括求值为函数对象的函数表达式,后跟一对圆括号,中间有逗号分隔的零或多个参数表达式列表。如果函数表达式是属性访问表达式,即函数是对象的属性或数组的元素,则是一个方法调用表达式。ES2020中,可以用f?.(x)的方式调用函数,如果f为null、undefined, 就不调用。

非严格模式下的函数调用,this是全局对象,严格模式下是undefined.

箭头函数总是继承自身定义所在环境的this值。

作为函数而非方法来调用的函数通常不会在定义中使用this, 但是可以用this关键字判断是不是处于严格模式。

方法调用:与函数调用的重要区别是,调用上下文。属性访问表达式由对象和属性名构成。在方法调用表达式中,对象会承认调用上下文,函数体通过this引用这个对象。方法调用一般使用点好进行属性访问,但也可以使用方括号语法。

不允许给this赋值。this关键字不具有变量那样的作用域机制,除了箭头函数,嵌套函数不会继承包含函数的this值。

构造函数调用:

构造函数调用与常规函数和方法调用的区别在于参数处理、调用上下文和返回值。构造函数调用会创建一个新的空对象,这个对象继承构造函数的prototype属性指定的对象。这个新创建的对象会被用作函数的调用上下文,因此可以在构造函数中通过this引用这个新对象。构造函数不使用return的时候, 这个新对象是构造函数表达式的值。如果构造函数显式使用了return 返回某个对象,那么这个对象就是调用表达式的值。如果构造函数return但没有返回值,或者返回一个原始值,那么这个返回值会被忽略,仍然以新创建的对象作为调用表达式的值。

间接调用:call和apply,是JavaScript函数对象的方法,可以用来间接调用函数,并指定调用时的this值。可以将任意函数作为任意对象的方法来调用,即使这个函数并不是该对象的方法。

隐式函数调用:

函数实参与形参

ES6及之后,可以在函数的形参列表中直接为每个参数定义默认值。语法为,“形参名=默认值”。如果有多个形参,可以使用前面的参数的值来定义后面的参数的默认值。

剩余形参:rest parameter. 允许调用时传入比形参多任意数量的实参的函数。剩余形参前面有3个点,而且必须是函数声明中最后一个参数。在调用有剩余形参的函数时,传入的实参会首先赋值给非剩余形参,然后所有剩余参数保存在一个数组中赋值给剩余形参。剩余形参的值始终是数组(可空)。

在ES6引入剩余形参之前,变长函数基于Arguments对象实现。一个类数组对象,允许通过数值而非名字取得传给函数的参数值。

剩余形参和扩展操作符经常同时出现。

对函数可以使用解构赋值技术。如果定义一个函数,形参名都包含在方括号中,说明这个函数期待对每对方括号传入一个数组值。传入的数组实参会被解构赋值为单独的命名形参。

function vectorAdd([x1, y1], [x2, y2]) {
    return [x1 + x2, y1 + y2];
}
vectorAdd([1,2],[3,4])

类似还有对传入的对象解构赋值。

function vectorMultiply({x,y}, scalar) {
    return {x: x*scalar, y: y*scalar};
}
vectorMultiply({x:1, y:2}, 2)

js中不支持python中的关键字参数这样的语法,但可以通过把对象参数解构为函数参数来模拟。

解构数组时,可以为被展开数组中的额外元素定义一个剩余形参

函数作为值

函数是一等公民。

函数在JavaScript中并不是原始值,而是一种特殊的对象,可以有自己的属性。

函数作为命名空间

函数体内声明的变量在函数外部是不可见的,为此可以把函数用作临时的命名空间。在一个表达式中定义并调用匿名函数的技术,叫做立即调用函数表达式,IIFE。

(function() {
    //code here
}());

函数作为命名空间真正的用武之地是,在命名空间中定义一个或多个函数,这些函数使用该命名空间中的变量,最后这些函数作为命名空间函数的返回值从内部传递出来,即闭包。

闭包

词法作用域:函数执行时使用的是定义函数时生效的变量作用域,而不是调用函数时生效的变量作用域。为了实现词法作用域,Js函数对象的内部状态不仅要包括函数代码,还要包括对函数定义所在作用域的引用。这种函数对象与作用域组合起来解析函数变量的机制,在编程语言中叫做闭包。严格来讲,所有的JS函数都是闭包。但由于多数函数调用与函数定义在同一作用域,所以闭包的存在无关紧要。当定义函数与调用函数的作用域不同时,闭包就起作用了。如一个函数返回了在它内部定义的嵌套函数。

闭包的强大:会捕获自身定义所在外部函数的局部变量(及参数)绑定。

let uniqueInteger = (function() {
    let counter = 0;
    return function() {return counter++;};
}());
uniqueInteger()
uniqueInteger()
function counter() {
    let n = 0
    return {
        count: function() { return n++; },
        reset: function() { n = 0; }
    };
}

函数属性、方法与构造函数

函数有一个只读的length属性,表示函数的元数,即形参个数,不包括剩余形参。

name: 定义函数时使用的名字。如果未命名函数,则name是第一次创建这个函数时赋值的变量/属性名。

prototype: 原型对象。

call和apply: 间接调用一个函数。f.call(o); f.apply(o); 都类似于下面的代码: o.m = f; o.m(); delete o.m; 如果对箭头函数调这两个方法,则第一个形参会被忽略,因为箭头函数从自己的上下文中继承this. call和apply区别在于对待其余参数的方式不同。call(o, 1, 2); apply(o, [1,2])

bind方法:把函数绑定到对象。

function f(y) { return this.x + y; }
let o = {x: 1};
let g = f.bind(o);
g(2) //3
let p = {x : 10, g};
p.g(2) //3: g仍然绑定到o而不是p. 

箭头函数的this不会被bind覆盖。

bind函数的其他用途:第一个参数是要绑定的上下文,其他传给bind的参数,也会随着this值一起被绑定,这个叫做函数的柯里化。

let sum = (x, y) => x + y;
let succ = sum.bind(null, 1) //x=1
succ(2) //3

function f(y, z) { return this.x + y + z; }
let g = f.bind({x: 1}, 2);
g(3) //6; this.x=1,y=2,z=3

toString方法:略。

Function构造函数:const f = new Function("x", "y", "return x*y;")

构造函数里,不接受任何指定新函数名字的参数,即创建的是匿名函数。

非常重要的一点,即构造函数创建的函数对象不适用词法作用域,而是始终编译为如同顶级函数一样。

函数式编程

Js不是Lisp/Haskell, 但可以使用函数式编程的技巧。

高阶函数:接收一个函数作为参数,并返回一个新的函数。

function not(f) {
    return function(...args) {
        let result = f.apply(this, args);
        return !result;
    }
}
function partialLeft(f, ...outerArgs) {
    return function(...innerArgs) {
        let args = [....outerArgs, ...innerArgs];
        return f.apply(this, args);
    };
}

function partialRight(f, ...outerArgs) {
    return function(...innerArgs) {
        let args = [ ...innerArgs, ....outerArgs];
        return f.apply(this, args);
    };
}

function partial(f, ...outerArgs) {
    return function(...innerArgs) {
        let args = [...outerArgs];
        let innerIndex = 0;
        for (let i = 9; i < args.length; i++) {
            if (args[i] === undefined) args[i] = innerArgs[innerIndex++];
        }
        args.push(...innerArgs.slice(innerIndex));
        return f.apply(this, args);
    };
}

发表回复

*您的电子邮件地址不会被公开。必填项已标记为 。

*
*