javascript权威指南笔记(第8章 函数)

Posted on 2014-09-10 11:18  liguwe  阅读(191)  评论(0编辑  收藏  举报

8.1 函数定义:

  变量声明提前,但是变量赋值并不会提前

  函数声明语句不能出现在循环,条件判断或者try/catch/finally以及with语句中:


8.2 函数调用(函数调用,方法调用,构造函数调用,间接调用(call,apply))

  1、var isStrict = (function() { return !this; }());

  2、this的指向问题(嵌套函数this的指向问题)

    可以解决的方法有:var self = this 或者使用 apply

//嵌套函数
var o = {
    m: function () {
        var self = this;                   //将this的值保存在一个变量中
        console.log(this === o);
        f();
        function f() {
            console.log(this === o);                  //false   this的值是全局对象或undefined
            console.log(self === o);            //true
        }
    }
};
o.m();

 


8.3 函数的实参与形参

  1、可选参数

function getPropertyNames(o, /* optional */ a) {
    if (a === undefined) a = [];
    for (var property in o) a.push(property);
    return a;
}

var a = getPropertyNames(o);
getPropertyNames(p, a);

  可以使用a = a || [] 来代替上面的if语句,但是a必须是预先声明的,因为a是作为参数传入的,即相当于var a;所以这里也可以这么使用  

  2、可变长的实参列表,实参对象arguments,它是一个类数组对象

    实参对象的属性:length/callee/caller

function max(/* ... */) {
    //最小值
    var max = Number.NEGATIVE_INFINITY;
    for (var i = 0; i < arguments.length; i++)
        if (arguments[i] > max) max = arguments[i];
    return max;
}
var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6);  // => 10000

 

    在非严格模式下,对实参对象arguments对象进行操作会影响到传入的参数,如删除其中一个元素或是改变length属性等都会影响到参数

    严格模式下,对caller或是callee进行读写操作都会产生一个类型错误:

    callee:指代当前正在执行的函数

    calller:指代调用当前正在执行的函数的函数

   3、将对象属性用作实参

      参数的顺序问题:

//这里需要记住参数顺序
function arraycopy(/* array */ from, /* index */ from_start, /* array */ to, /* index */ to_start, /* integer */ length) {
    //code goes here
}

//这个版本效率低,但是不用记住参数顺序
function easycopy(args) {
    arraycopy(args.from,
              args.from_start || 0,  // Note default value provided args.to,
              args.to_start || 0,
              args.length);
}
// Here is how you might invoke easycopy():
var a = [1, 2, 3, 4], b = [];
easycopy({from: a, to: b, length: 4});

 

4、实参类型

  传入参数时,先检测传入的参数

function sum(a) {
    if (isArrayLike(a)) {            //这个函数在第七章
        var total = 0;
        for (var i = 0; i < a.length; i++) {
            var element = a[i];
            if (element == null) continue;
            if (isFinite(element)) total += element;
            else throw new Error("sum(): elements must be finite numbers");
        }
        return total;
    }
    else throw new Error("sum(): argument must be array-like");
}

//尽可能的在抛出异常之前将非数字转换为数字
function flexisum(a) {
    var total = 0;
    for (var i = 0; i < arguments.length; i++) {
        var element = arguments[i], n;
        if (element == null) continue;
        if (isArray(element))
            n = flexisum.apply(this, element);
        else if (typeof element === "function") n = Number(element());
        else n = Number(element);
        if (isNaN(n))  throw Error("flexisum(): can't convert " + element + " to number");
        total += n;
    }
    return total;
}

 


8.4 作为值的函数

   注意这种形式函数的调用: 

var a = [function(x) { return x*x; }, 20];      // An array literal 
a[0](a[1]);                                      // => 400 

   定义函数属性: 

//每次调用这个函数会返回不同的整数
uniqueInteger.counter = 0;
function uniqueInteger() {
    return uniqueInteger.counter++;
}

//计算阶乘,并将交过缓存至函数的属性中
function factorial(n) {
    if (isFinite(n) && n > 0 && n == Math.round(n)) {
        if (!(n in factorial))
            factorial[n] = n * factorial(n - 1);
        return factorial[n];
    }
    else return NaN;
}
factorial[1] = 1;

 


 

8.5 作为命名空间的函数

  立即调用函数:不污染全局命名空间

  返回修正 ie bug 的extend版本

//返回修正 ie bug 的extend版本:即在ie的多数版本中,如果0的属性有一个不可枚举的同名属性,则for/in中不会枚举对象o的可枚举属性;也就是说不会处理租入toString()的属性
var extend = (function () {
    //非ie
    for (var p in {toString: null}) {
        return function extend(o) {
            for (var i = 1; i < arguments.length; i++) {
                var source = arguments[i];
                for (var prop in source) o[prop] = source[prop];
            }
            return o;
        };
    }
//如果执行到这里,则说明不会for/in循环不会枚举测试对象的toString属性
return function patched_extend(o) {
    for (var i = 1; i < arguments.length; i++) {
        var source = arguments[i];
        for (var prop in source) o[prop] = source[prop];
        for (var j = 0; j < protoprops.length; j++) {
            prop = protoprops[j];
            if (source.hasOwnProperty(prop)) o[prop] = source[prop];
        }
    }
    return o;
};
var protoprops = ["toString", "valueOf", "constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString"];
}()
);

 


8.6 闭包

  当调用函数时的闭包所指向的作用域链和定义函数时的作用域链不是同一个作用域链时:

  首先比较下面两个例子的不同:

//例子一
var scope = "global scope";
function checkscope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f();
}
checkscope()      // => "local scope"
//例子二
var scope = "global scope";
function checkscope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f;
}
checkscope(); //    function f() {return scope;}
checkscope()();  //local scope

 

   重写uniqueInteger:这样除了内部的函数可以访问counter变量,其他任何代码都不能访问它

var uniqueInteger = (function () {
    var counter = 0;
    return function () {
        return counter++;
    };
}());

 

   代码:每次调用counter()都会创建新的作用域链和新的私有变量  

function counter() {
    var n = 0;
    return {
        count: function () {
            return n++;
        },
        reset: function () {
            n = 0;
        }
    };
}
// Create two counters
var c = counter(),
    d = counter();
c.count()                    // => 0
d.count()                   // => 0: they count independently
c.reset()                   // reset() and count() methods share state
c.count()                   // => 0: because we reset c
d.count()                   // => 1: d was not reset

 

    将上面的闭包合并为属性存储器方法setter和getter  

function counter(n) {
    return {
        get count() {
            return n++;
        },
        set count(m) {
            if (m >= n) n = m;
            else throw Error("count can only be set to a larger value");
        }
    };
}
var c = counter(1000);
c.count;       //1000
c.count;       //1001
c.count = 2000;
c.count        //2000
c.count        //error

 

    利用闭包技术来共享的私有状态的通用做法:

    notes:所操作的属性值并没有存储在对象o中,不能绕过存储器方法来设置或修改这个值    

function addPrivateProperty(o, name, predicate) {
    var value;
    o["get" + name] = function () {
        return value;
    };
    o["set" + name] = function (v) {
        if (predicate && !predicate(v))
            throw Error("set" + name + ": invalid value " + v);
        else
            value = v;
    };
}
var o = {};
addPrivateProperty(o, "Name", function (x) {
    return typeof x == "string";
});
o.setName("Frank");
console.log(o.getName());
o.setName(0);               //试图设置一个类型错误值

 

    再比较下面两段代码:  

function constfunc(v) {
    return function () {
        return v;
    };
}
var funcs = [];
for (var i = 0; i < 10; i++) funcs[i] = constfunc(i);
funcs[5]()         //5

    
//这里创建了十个闭包
function constfuncs() {
    var funcs = [];
    for (var i = 0; i < 10; i++)
        funcs[i] = function () {
            return i;
        };
    return funcs;
}
var funcs = constfuncs();
funcs[5]()     //都是10 

 

    书写闭包时要注意的一个问题:如何在闭包中访问外部的this值和argument值:

      var that = this;

      var outerArguments = arguments;


8.7 函数属性,方法和构造函数

1.length属性:

//注意这个函数并不能在严格模式下工作
function check(args) {
    var actual = args.length;
    var expected = args.callee.length;
    if (actual !== expected) throw Error("Expected " + expected + "args; got " + actual);
}
function f(x, y, z) {
    check(arguments);
    return x + y + z;
}

2、prototype

3、apply和call方法

  var biggest = Math.max.apply(Math, array_of_numbers);

  注意:apply传入的数组可以类数组对象也可以是真实数组

  monkey-patching:动态修改已有方法,如下代码所示:

function trace(o, m) {
    var original = o[m]; //在闭包中保存原始方法 
    o[m] = function () { // Now define the new method.
        console.log(new Date(), "Entering:", m);
        var result = original.apply(this, arguments);
        console.log(new Date(), "Exiting:", m);
        return result;
    };
}

 

4、bind()方法

代码:

function bind(f, o) {
    if (f.bind) return f.bind(o);
    else return function () {
        return f.apply(o, arguments);
    };
}

 

函数式编程技术:看下面两个函数

var sum = function (x, y) {
    return x + y
};
var succ = sum.bind(null, 1);
succ(2) // => 3: x is bound to 1, and we pass 2 for the y argument

function f(y, z) {
    return this.x + y + z
}; // Another function that adds 
var g = f.bind({x: 1}, 2); // Bind this and y
g(3) // => 6: this.x is bound to 1, y is bound to 2 and z is 3

 

ES3中模拟Es5中的bind()方法:

if (!Function.prototype.bind) {
    Function.prototype.bind = function (o /*, args */) {
        var self = this,                      //下面的闭包中使用它
            boundArgs = arguments;            //这样可以在后面嵌套的函数中使用它
        return function () {

            var args = [], i;
            for (i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
            for (i = 0; i < arguments.length; i++) args.push(arguments[i]);   //这里的arguments是闭包内的arguments

            return self.apply(o, args);
        };
    };
}

 

  但是在ES5中bind()的一些特性是无法模拟的。例如

  toString方法:

大多数toString()方法返回的是完整的源码,内置函数往往是“[native code]”

 

使用Function构造函数

  关于Function构造函数的几个注意点:

  注意点:使用Function()构造函数并不需要通过传入实参已指定函数名,另外函数体的代码的编译总是会在顶层函数执行,正如下面的代码所示:

  Function()构造函数认为是在全局作用于中执行的eval 

var scope = "global";
function constructFunction() {
    var scope = "local";
    return new Function("return scope");
}
constructFunction()(); // => "global"

 

  可调用的对象:

    比较:类数组对象----数组

       可调用对象----函数(肯定都是可调用的)

    如typeof RegExp; 有些返回function,有些返回object

  

isFunction()函数:

function isFunction(x) {
return Object.prototype.toString.call(x) === "[object Function]";
}

 

 


 

8.8  函数式编程

使用函数处理数组:

非函数式编程:

//计算平均数
var data = [1, 1, 3, 5, 5];
var total = 0;
for (var i = 0; i < data.length; i++) total += data[i];
var mean = total / data.length;
//计算标准差
total = 0;
for (var i = 0; i < data.length; i++) {
    var deviation = data[i] - mean;
    total += deviation * deviation;
}
var stddev = Math.sqrt(total / (data.length - 1));

 

使用函数式编程实现更简洁的编程:

var sum = function (x, y) {
    return x + y;
};
var square = function (x) {
    return x * x;
};
var data = [1, 1, 3, 5, 5];
var mean = data.reduce(sum) / data.length;
var deviations = data.map(function (x) {
    return x - mean;
});
var stddev = Math.sqrt(deviations.map(square).reduce(sum) / (data.length - 1));

 

ES3中的map()与reduce():

var map = Array.prototype.map
    ? function (a, f) {
    return a.map(f);
} : function (a, f) {
    var results = [];
    for (var i = 0, len = a.length; i < len; i++) {
        if (i in a) results[i] = f.call(null, a[i], i, a);
    }
    return results;
};

var reduce = Array.prototype.reduce
    ? function (a, f, initial) {
    if (arguments.length > 2)
        return a.reduce(f, initial);
    else return a.reduce(f);
}
    : function (a, f, initial) {
    var i = 0, 
        len = a.length,
        accumulator;
    if (arguments.length > 2) accumulator = initial;
    else { 
        if (len == 0) throw TypeError();
        while (i < len) {
            if (i in a) {
                accumulator = a[i++];
                break;
            }
            else i++;
        }
        if (i == len) throw TypeError();
    }
//Now call f for each remaining element in the array
    while (i < len) {
        if (i in a)
            accumulator = f.call(undefined, accumulator, a[i], i, a);
        i++;
    }
    return accumulator;
};

 

使用上面的map()与reduce(): 注意这里是将data传入

var data = [1,1,3,5,5];
var sum = function(x,y) { return x+y; };
var square = function(x) { return x*x; };
var mean = reduce(data, sum)/data.length;
var deviations = map(data, function(x) {return x-mean;});
var stddev = Math.sqrt(reduce(map(deviations, square), sum)/(data.length-1));

 

 

高阶函数:

  高阶函数就是操作函数的函数,它接受一个或多个函数并返回一个新函数

  例子:

function not(f) {
    return function () {
        var result = f.apply(this, arguments);
        return !result;
    };
}
var even = function (x) {
    return x % 2 === 0;
};
var odd = not(even);
[1, 1, 3, 5, 5].every(odd);  //true 每个元素都是奇数

 

 

 

 

 

不完全函数

 

记忆: