《JavaScript编程精粹》——函数篇

函数对象

  • JavaScript中函数就是对象
  • 函数对象连接的原型是Function.prototype
  • 函数在创建时也有一个prototype,其拥有一个constructor属性,值是一个函数

函数显式创建

  • 普通函数function funcName( /*参数*/ ) { /*代码*/ }
  • 匿名函数function ( /*参数*/ ) { /*代码*/ }

调用

  • 每个函数会接受两个附加的参数
    • this
      • this的值取决于调用的模式
    • arguments
      • 调用函数时参数的集合,实参和形参的个数不匹配时不会报错,如果参数值过多,超出的参数值会被忽略,反之,没有被赋值的参数默认为undefined
  • 四种调用模式
    • 方法调用模式
      • 将函数保存为一个对象的属性,也称为方法,该方法被调用时,this被绑定到该对象
    • 函数调用模式
      • 该函数不是一个对象的属性时,其是被当作一个函数来调用的
      • 这种调用模式的this自始至终都与全局对象Window绑定(这是一个坑,请看代码注释)
    • 构造器调用模式
      • 在函数前带一个new来调用,即new 函数名()
      • 背后会创建一个对象,作为该函数prototype成员的新对象,this也是绑定到这个对象
      • new前缀会改变return语句的行为
    • Apply调用模式
      • JavaScript是一门函数式编程语言,所以可以拥有方法
      • apply方法接收两个参数
        1. 要绑定给this的值
        2. 参数数组
/**
 *  方法调用模式: 将函数保存为一个对象的属性,也称为方法,该方法被调用时,this被绑定到该对象
 */

var myObject = {
    value: 0,
    //increment是对象myObject的一个属性,也是一个函数
    increment: function () {
        //此处的this等同于对象myObject,即绑定
        this.value += 1;
        return this.value;
    }
};

myObject.increment();//方法调用


/**
 *  函数调用模式: 
 *              该函数不是一个对象的属性时,其是被当作一个函数来调用的
 *              这种调用模式下的被调用函数的this自始至终都与全局对象Window绑定
 */
function add(val1, val2) {
    return val1 + val2;
}

var myObject1 = {
    value: 1,
    /* 错误示范 */
    double: function () {
        var helper = function () {
            //此处的this并不是与myObject1绑定,而是Window,所以并没有改变myObject1的value
            this.value = add(this.value, this.value);
        };

        //函数调用模式,helper函数的this是全局对象
        helper();
    },

    /* 修正后 */
    double_fixed: function () {
        //double_fixed是以方法模式调用的,所以this与myObject1绑定,可以用于操作myObject1的属性
        var that = this;

        var helper = function () {
            //注意这是that不是this
            that.value = add(that.value, that.value);
        };

        //函数调用模式
        helper();
    }
};

myObject1.double();
console.log(myObject1.value); //值是1

myObject1.double_fixed();
console.log(myObject1.value); //值是2


/**
 *  构造器调用模式: 
 *              在函数前带一个new来调用,即new 函数名()
 *              背后会创建一个对象,作为该函数prototype成员的新对象,this也是绑定到这个对象
 *              new前缀会改变return语句的行为(不过构造器一般没有返回值)
 */

var Quo = function (string) {
    this.status = string;

    // return new Boolean(false); new前缀会导致new Quo('confused)返回的是一个Boolean对象而不是Quo
}

Quo.prototype.get_status = function () {
    return this.status;
}

//构造一个Quo实例
var myQuo = new Quo('confused');
//console.log(myQuo);
console.log(myQuo.get_status()); //值是confused


/**
 *  Apply调用模式: 
 *              在函数前带一个new来调用,即new 函数名()
 *              背后会创建一个对象,作为该函数prototype成员的新对象,this也是绑定到这个对象
 *              new前缀会改变return语句的行为(不过构造器一般没有返回值)
 */

function mul(val1, val2) {
    return val1 * val2;
}

var array = [3, 4];
var mul = mul.apply(null, array); //类似mul(3, 4)

console.log(mul); //值为12

//关于第一个参数的用法

var appObject = function () {};
appObject.prototype.get_money = function () {
    return this.money;
}

var moneyObject = {
    money: 1024
};

var money = appObject.prototype.get_money.apply(moneyObject);

console.log(money); //值为1024,即moneyObject的money成了appObject的属性,所以调用get_money时获取到了1024

参数arguments

  • 可以理解为可变参数,接受任意多个参数
  • 无论有没有形参,接收的参数在arguments都可以找到
var sum = function (arg1, arg2) {
    console.log(arg1); //值是1
    console.log(arg2); //值是2
    var i, sum = 0;
    for (i = 0; i < arguments.length; i += 1) {
        sum += arguments[i];
    }
    return sum;
};

console.log(sum(1, 2, 3, 4, 5)); //值是15

扩充类型

JavaScript允许给语言的基本类型扩充功能,如之前的通过prototype添加方法,例子

Function.prototype.method = function (name, func) {
    //基本类型的原型是公用结构,确保没有该方法时才添加
    if (!this.prototype[name]) {
        this.prototype[name] = func;
    }
    return this;
}


//给整数类型添加取整
Number.method('integer', function () {
    return Math[this < 0 ? 'ceil' : 'floor'](this);
});

console.log((3.3).integer()) //3
console.log((-3.3).integer()) //-3

作用域

var a = 3;
var foo = function () {
    var a = 4,//当foo被调用时,这里的a覆盖3
        b = 5;
    var bar = function () {
        var b = 7,//当bar被调用时,这里的b覆盖5
            c = 11;
        a += b + c; //a = 4 b = 7 c = 11
    };
    bar();
    console.log(a); //值为22
};
foo();

闭包

  • 内部函数可以访问外部函数的参数和变量
    • 但不包括this和arguments,就像之前看到的,在外部函数里以函数调用模式调用内部函数,内部函数的this是全局对象
    • 内部函数拥有自己的arguments,所以无法[直接访问]外部函数的arguments,可以间接访问
  • 外部函数的作用域对其他程序不可见
  • 正面例子
//此处直接运行匿名函数: (function(){}())
//所以myObject是return返回的一个对象而不是一个函数
var myObject = (function () {
    var value = 0;
    return {
        increment: function (inc) {
            //访问外部函数的value
            value += 1;
        },
        getValue: function () {
            //访问外部函数的value
            return value;
        }
    };
}());

console.log(myObject.value); //undefined  ===>  无法直接访问到value
console.log(myObject.getValue()); //值为1
myObject.increment();
console.log(myObject.getValue()); //值为2



//关于之前利用prototype添加get_status方法的改善
//之前的做法 quo.prototype.get_status = function () {};
//现在的做法
var quo = function (status) {
    return {
        get_status: function () {
            return status;
        }
    };
};
var myQuo = quo("amazed"); //利用闭关可以避免使用new创建对象
console.log(myQuo.get_status()); //值为amazed
  • 反面例子
//这个函数原来预期每个节点被点击时显示不同的编号
//但实际上所有的值都是nodes.length
//因为onclick是直接访问外部的i,而不是复制i变量
//循环结束后,i = nodes.length,所以onclick访问i时值都是nodes.length
var add_the_handlers = function (nodes) {
    var i;
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function (e) {
            alert(i);
        };
    }
};
  • 改良例子
var add_the_handlers_fixed = function (nodes) {
    var i;

    //i是形参,所以每次调用都有一个新的i被保留起来
    //返回结果是点击事件需要的函数,利用闭包可以访问外部变量,直接alert(i)
    var helper = function (i) {
        return function (e) {
            alert(i);
        };
    };

    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = helper(i);
    }

}

模块化

这是利用闭包实现的模块化,如果有Java经验就会发现,这等同于将成员变量私有化

/*
 * 这个实现避免了seq和prefix成为全局变量
 */

var serial_maker = function () {
    var prefix = ''; //私有化,外部无法访问
    var seq = 0;

    return {
        set_prefix: function (p) {
            prefix = String(p);
        },
        set_seq: function (s) {
            seq = s;
        },
        gensym: function () {
            var result = prefix + seq;
            seq += 1;
            return result;
        }
    }
};

var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);

console.log(seqer.gensym()); //Q1000
console.log(seqer.gensym()); //Q1001

级联

在修改某些属性令方法返回this,得以继续操作对象

var myObject = function () {
    var value = 0;
    return {
        init: function () {
            value = 20;
            return this;
        },
        add: function (val) {
            //延伸问题:如果形参是value,与外部的value同名,那么该如何访问外部的value?
            value += val;
            return this;
        },
        increment: function () {
            value += 1;
            return this;
        },
        getValue: function () {
            return value;
        }
    }
};
var obj = myObject();
var value = obj.init().add(1).add(2).increment().add(6).getValue();
console.log(value); //值为30

柯里化

第一次接触柯里化懵懵懂懂,柯里化函数就是将每次调用时的参数与原函数结合起来,产生了一个已有参数,又可以接收更多参数的函数,用以继续调用。这是非常浅的理解了。

Function.prototype.method = function (name, func) {
    //基本类型的原型是公用结构,确保没有该方法时才添加
    if (!this.prototype[name]) {
        this.prototype[name] = func;
    }
    return this;
}

function add() {
    var sum = 0;
    for (var i = 0; i < arguments.length; i += 1) {
        sum += arguments[i];
    }
    return sum;
}

Function.method('curry', function () {
    var slice = Array.prototype.slice,
        args = slice.apply(arguments),
        that = this;
    return function () {
        return that.apply(null, args.concat(slice.apply(arguments)));
    };
});

//add1已有参数1
var add1 = add.curry(1);
//add1已有参数1,2
add1 = add1.curry(2);
console.log(add1(2)); //值是5

记忆

将函数先前运算的结果保留,避免重复运算。这是一种思想,不是JavaScript特有的,在算法中称为动态规划。

/* 重复运算版本 */
var fibonacci = function (n) {
    if (n < 2) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

var fibonacci_cache = function () {
    var memo = [0, 1];
    var fib = function (n) {
        //如果memo[n]不是未定义,说明已经有结果,无需再次运算
        var result = memo[n];
        if (typeof result !== 'number') {
            result = fib(n - 1) + fib(n - 2);
            memo[n] = result;
        }
        return result;
    };
    return fib;
}();


console.log(fibonacci(5));
console.log(fibonacci_cache(6));
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值