函数与函数式编程

一、函数在实际开发中的应用

1.函数声明

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

函数声明:

function fn{...}

之前学习了,在变量对象的创建过程中,函数声明比变量声明优先级更高,即我们常常提到的函数声明提前。复习一下:

 

所以,在执行上下文中,无论在哪里进行了函数声明,我们都可以在同一个执行上下文中直接使用该函数。、

2.函数表达式

函数表达式与函数声明不同。函数表达式使用var进行声明,所以在确认它是否能正确使用的时候就必须要依照var的规则进行判断。

函数表达式:

var fn = function (){...}

使用var进行变量声明,其实是进行了两步操作:

(1)在变量对象中以变量名建立一个属性,属性值为undefined

(2)变量赋值

用var对函数进行赋值也是一样。例子:

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

上述代码执行顺序为:

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

一些赋值操作也是属于函数表达式。比如给原型添加方法。

所以,声明方式的不同,导致了函数声明和函数表达式在使用上有一些差异,除此之外,在使用上并无不同。

3.匿名函数

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

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

4.函数自执行与块级作用域

ES6中用let命令新增了块级作用域,外层作用域无法访问内层作用域,即使内外使用相同的变量名 ,也互不干扰在ES5中,只有全局作用域和函数作用域,没有块级作用域。

因此常常使用函数自执行的方式来模仿块级作用域。

(function() {
   // ...
})();

可以看出函数自执行,其实也是匿名函数的一种应用。这样就提供了一个独立的执行上下文,结合闭包,就为模块化提供了基础。

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

私有变量和方法很容易创建,但因为作用域链的单向访问,外部执行环境是无法访问内部的任何变量与方法的,那如何创建公有的变量与方法呢。

就是利用闭包,只需要根据闭包的定义,创建一个闭包,将你认为需要公开的变量和方法开放出来即可。

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

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

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

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

jQuery中的模块与闭包:

// 使用函数自执行的方式创建模块
(function(window, undefined) {

    // 声明jQuery构造函数
     var jQuery = function(name) {

        // 主动在构造函数中,返回一个jQuery实例
         return new jQuery.fn.init(name);
     }

    // 添加原型方法
     jQuery.prototype = jQuery.fn = {
         constructor: jQuery,
         init:function() { ... },
         css: function() { ... }
     }
     jQuery.fn.init.prototype = jQuery.fn;

    // 将jQuery改名为$,并将引用保存在window上,形成闭包,对外开发jQuery构造函数,这样我们就可以访问所有挂载在jQuery原型上的方法了
     window.jQuery = window.$ = jQuery;
 })(window);

// 在使用时,我们直接执行了构造函数,因为在jQuery的构造函数中通过一些手段,返回的是jQuery的实例,所以我们就不用再每次用的时候在自己new了
$('#div1');

状态管理器

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

基本数据类型与引用数据类型在复制上的差异:基本数据类型复制,是直接值发生了复制,因此改变后,各自相互不影响。但是引用数据类型的复制,是保存在变量对象中的引用发生了复制,因此复制之后的这两个引用实际访问的实际是同一个堆内存中的值。

当值作为函数的参数传递进入函数内部时,也有同样的差异。

函数的参数在进入函数后,实际上是保留在了函数的变量对象中,因此,这个时候相当于发生了一次复制。

var a = 20;

function fn(a) {
    a = a + 10;
    return a;
}
fn(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 }

不是很好理解,看了《JavaScript高级程序设计》p70,下面是我的理解:

在第一个例子中,调用fn函数时,变量a作为一个参数被传递给函数,这个变量a的值为20。于是,数值20就被赋值给形参a,以便在fn中使用。在函数内部,a作了运算,但这一变化不会影响函数外部的a变量。参数a和变量a互不认识,它们仅仅具有相同的值。并且后面打印的a值只可能是全局的a值。

第二个例子比第一个例子稍难理解,不过只要知道牢牢记住:在变量作为函数的参数传递时是按值传递的,那也就迎刃而解了。首先,上述代码创建了一个对象,并保存在了a中,它有着自己的地址。a对象作为参数被传递给函数,把地址的值传递给参数a,这里就是按值传递。这个时候参数a和变量a引用的是同一个对象。于是,在对函数内部的a作更改的时候,外部的a也会有所反映。

再看个例子加深理解:

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

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

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

上述代码跟之前的代码相比,增加了两行。将person的引用地址按值传递给obj后,又将obj的引用指向另一个值,这时候改变obj的值并不会影响person。这也说明了,只是将person的地址按值传递给了obj。如果是按引用进行传递的话,obj的引用值更改后,person也会指向新的地址,而现实是person的地址值从未改变过,只是进行了复制。

三、函数式编程

JavaScript不是一门纯函数式编程的语言,但是它使用了许多函数式编程的特性。

函数式编程的思维建议我们将这种会多次出现的功能封装起来以备调用。

函数式编程思维具有以下几个特征:

(1)函数是第一等公民。setTimeout问题

(2)只用“表达式”,不用“语句”。表达式是一个单纯的运算过程,总是有返回值;而语句是执行某种操作,没有返回值。函数式编程期望一个函数有输入,有输出,利用return。

(3)相同的输入总会得到相同的输出,不产生副作用。副作用指函数内部与外部互动,产生运算意外的其它结果。

(4)闭包

(5)柯里化

posted @ 2019-09-04 21:58  二猫子  阅读(270)  评论(0编辑  收藏  举报