函数和函数式编程

@(js进阶)

函数与函数式编程

一.几个基本概念

函数声明:

JavaScript中有两种声明方式,一个是使用var的变量声明,另一个就是使用function的函数声明

变量对象的创建过程中,函数声明比变量声明具有更为优先的执行顺序,即我们常常提到的函数声明提前。因此我们在执行上下文中,无论在什么位置声明了函数,我们都可以在同一个执行上下文中直接使用该函数。

函数表达式

函数表达式使用var进行声明,因此函数表达式是否可以使用遵循变量声明的规则,变量声明其实是分为两个步骤

// 变量声明
var a = 20;

// 实际执行顺序
var a = undefined;  // 变量声明,初始值undefined,变量提升,提升顺序次于function声明
a = 20;  // 变量赋值,该操作不会提升

函数表达式的提升方式与变量声明一致

fn(); // 报错
var fn = function() {
    console.log('function');
}

上面的例子的执行顺序是

var fn = undefined;   // 变量声明提升
fn();    // 执行报错
fn = function() {   // 赋值操作,此时将后边函数的引用赋值给fn
    console.log('function');
}

匿名函数

匿名函数,顾名思义,就是指的没有被显示进行赋值操作的函数。它的使用场景,多作为一个参数传入另一个函数中。

var a = 10;
var fn = function(bar, num) {
    return bar() + num;
}

fn(function() {
    return a;
}, 20)

我们没有办法在外部执行上下文中引用到它,但是在fn函数内部,我们将该匿名函数赋值给了变量bar,保存在了fn变量对象的arguments对象中。

// 变量对象在fn上下文执行过程中的创建阶段
VO(fn) = {
    arguments: {
        bar: undefined,
        num: undefined,
        length: 2
    }
}

// 变量对象在fn上下文执行过程中的执行阶段
// 变量对象变为活动对象,并完成赋值操作与执行可执行代码
VO -> AO

AO(fn) = {
    arguments: {
        bar: function() { return a },
        num: 20,
        length: 2
    }
}

由于匿名函数传入另一个函数之后,最终会在另一个函数中执行,因此我们也常常称这个匿名函数为回调函数

函数自执行与块级作用域

在ES5中,没有块级作用域,因此我们常常使用函数自执行的方式来模仿块级作用域,这样就提供了一个独立的执行上下文,结合闭包,就为模块化提供了基础。

一个模块往往可以包括:私有变量、私有方法、公有变量、公有方法。

根据作用域链的单向访问,可以很容易知道在这个独立的模块中,外部执行环境是无法访问内部的任何变量与方法的,因此我们可以很容易的创建属于这个模块的私有变量与私有方法。

(function() {
    // 私有变量
    var age = 20;
    var name = 'Tom';

    // 私有方法
    function getName() {
        return `your name is ` + name;
    }
})();

利用闭包,我们可以访问到执行上下文内部的变量和方法,因此,我们只需要根据闭包的定义,创建一个闭包,将你认为需要公开的变量和方法开放出来即可。

(function() {
    // 私有变量
    var age = 20;
    var name = 'Tom';


    // 私有方法
    function getName() {
        return `your name is ` + name;
    }


    // 共有方法
    function getAge() {
        return age;
    }

    // 将引用保存在外部执行环境的变量中,形成闭包,防止该执行环境被垃圾回收
    window.getAge = getAge;
})();

详细见

二、函数参数的传递方式:按值传递

基本类型复制,是直接值发生了复制,因此改变后互不影响;然而对象变量复制,是引用地址的复制,因此复制之后两个变量指向同一个引用地址,两者中一个改变另一个也改变。

var a = 20;
var b = a;
b = 10;
console.log(a);  // 20

var m = { a: 1, b: 2 }
var n = m;
n.a = 5;
console.log(m.a) // 5

函数的参数在进入函数后,实际是被保存在了函数的变量对象中,如argument中,因此,这个时候相当于发生了一次复制,如下例

var a = 20;
function fn(a) {
    a = a + 10;
    return a;
}
console.log(a); // 20
var a = { m: 10, n: 20 }
function fn(a) {
    a.m = 20;
    return a;
}

fn(a);
console.log(a);   // { m: 20, n: 20 }

实际上结论仍然是按值传递,只不过当我们期望传递一个引用类型时,真正传递的,只是这个引用类型保存在变量对象中的引用而已,如下例:

var person = {
    name: 'Nicholas',
    age: 20
}

function setName(obj) {  // 传入一个引用
    obj = {};   // 将传入的引用指向另外的值
    obj.name = 'Greg';  // 修改引用的name属性
}

setName(person);
console.log(person.name);  // Nicholas 未被改变

在上面的例子中,如果person是按引用传递,那么person就会自动被修改为指向其name属性值为Gerg的新对象。但是我们从结果中看到,person对象并未发生任何改变,因此只是在函数内部引用被修改而已。

三、函数式编程

函数是第一等公民

指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数传入另一个函数,或者作为别的函数的返回值。如下例:

var a = function foo() {}  // 赋值
function fn(function() {}, num) {}   // 函数作为参数

// 函数作为返回值
function var() {
    return function() {
        ... ...
    }
}

只用“表达式”,不用“语句”

"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。

没有副作用

函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。

即所谓的只要是同样的参数传入,返回的结果一定是相等的。

闭包与 柯里化

四、函数封装

普通封装

function add(num1, num2) {
    return num1 + num2;
}

add(20, 10); // 30

挂载在对象上

if(typeof Array.prototype.add !== 'function') {
  Array.prototype.add = function() {
    var i = 0,
        len = this.length,
        result = 0;

    for( i=0; i < len; i++) {
        result += this[i]
    }
    return result;
  }
}

[1, 2, 3, 4].add() // 10

修改数组对象的例子,常在面试中被问到类似的,但是并不建议在实际开发中扩展原生对象。与普通封装不一样的是,因为挂载在对象的原型上我们可以通过this来访问对象的属性和方法,所以这种封装在实际使用时会有许多的难点,因此我们一定要掌握好this。

你可能感兴趣的:(函数和函数式编程)