• JavaScript


    在JavaScript中,函数即对象,程序可以随意操控它们。比如,JavaScript可以把函数赋值给变量,或者作为参数传递给其他函数,并且可以给它们设置属性,甚至调用它们的方法。

    函数定义

    一般函数定义的形式有以下几种:

    • 函数声明法
    function factorial(x) {
        if(x <= 1) return 1;
        return x * factorial(x - 1);
    }

    注意:函数声明语句”被提前”到脚本的顶部,所以可以在定义之前的代码调用函数。

    • 函数赋值法(可定义为匿名函数)
    var square = function(x) { return x*x; }

    注意:变量的声明是可以提前的,但是给变量的赋值不会提前,所以这种方式的函数在定义之前无法调用。

    • 函数定义后立即执行
    var tensquared = (function(x) { return x*x; }(10));

    注意: function左边的左括号是必需的,因为如果不写左括号,JavaScript解释器会将关键字function解析为函数声明语句。使用左括号后,JavaScript解释器才会正确地将其解析为函数定义表达式

    • 嵌套函数
    function hypotenuse(a, b) {
        function square(x) { return x*x; }
        return Math.sqrt( square(a) + square(b) );
    }

    注意:嵌套函数中,注意作用域的使用

    函数调用

    有4种方式可以调用JavaScript函数:

    • 作为函数
    • 作为方法
    • 作为构造函数
    • 通过call()和apply()方法间接调用

    作为函数调用

    函数调用就是直接通过function对象来调用函数。

    var probability = factorial(5) / factorial(13);

    根据ECMAScript3和非严格的ECMAScript5的规定,函数调用的上下文(this的值)是全局对象(window)。在ECMAScript 5的严格模式下,调用上下文是undefined。

    作为方法调用

    • 方法调用是通过对象的属性来调用函数。
    • 方法调用和函数调用最大的一个区别是:调用上下文。方法调用的上下文是调用它的对象,可以通过this关键字引用该对象。
    • this是一个关键字,不是变量,也不是属性名。JavaScript不允许给this赋值。
    var calculator = {
        operand1: 1,
        operand2: 2,
        add: function() {
            this.result = this.operand1 + this.operand2;
        }
    };
    
    calculator.add();       // 方法调用
    calculator["add"]();    // 另一种形式的方法调用
    calculator.result;      // 2,对象属性添加成功
    • 如果嵌套函数作为函数调用,则this值不是全局对象就是undefined。
    var o = {
      m: function() {
        var self = this;
        console.log(this === o);            // true
        f();                                // 作为函数调用
    
        function f() {
          console.log(this === o);          // false
          console.log(this === window);     // true
        }
      }
    }
    • 如果嵌套函数作为方法调用,则this值指向调用它的对象。
    var o = {};
    function outer() {
        var inner = function() {
            console.log(this === o);
        };
        o.m = inner;
    }
    outer();
    o.m();          // 输出: true,作为方法调用

    作为构造函数调用

    • 如果函数或方法调用之前带有关键字new,它就构成构造函数调用。
    • 构造函数调用和普通的函数调用以及方法调用在实参处理、调用上下文和返回值方面都有不同。
    • 构造函数调用会创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数使用这个新创建的对象作为调用上下文,并可以使用this关键字引用这个新创建的对象。
    var o = {
        m: function() {
            console.log(this === o);
        }
    };
    
    
    o.m();              // true,方法调用
    var s = new o.m();  // false,构造函数调用

    间接调用

    • JavaScript中的函数也是对象,函数对象也可以包含方法。其中的2个方法call()和apply()可以用来间接地调用函数。
    • 两个方法允许显式指定调用所需的this值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。
    • 在ECMAScript3和非严格模式中,传入的null和undefined都会被全局对象代替,而其他原始值则会被相应的包装对象所替代
    f.call(o);
    f.apply(o);

    函数的实参和形参

    JavaScript中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参值做任何类型检查。实际上,JavaScript函数调用甚至不检查传入形参的个数。函数对于未传入的参数赋值为undefined,对于多余的参数忽略

    可选形参

    由于调用函数传入的实参个数可以比形参个数少,所以函数应当对此有一个较好的适应性,给省略的参数赋一个合理的默认值。

    function getPropertyNames(o, /* optional */ a) {
        if (a === undefined)  a = [];    // 如果未定义,则使用新数组
    
        for (var property in o) 
            a.push(property);
    
        return a;
    }
    
    var a = getPropertyNames(o);    // 将o的属性存储到一个新数组中
    getPropertyNames(p, a);         // 将p的属性追加到数组a中

    其中第一个if判断语句可简写为:

    a = a || [];

    实参对象(arguments)

    • 实参对象是一个类数组对象,可以通过数字下标访问传入函数的实参值,而不用非要通过形参名字来得到实参。

    下面的函数可以接收任意数量的实参:

    function max(/* ... */) {
        var max = Number.NEGATIVE_INFINITY;
        // 遍历实参,查找并记住最大值
        for(var i = 0; i < arguments.length; i++) 
            if(arguments[i] > max) max = arguments[i];
    
        return max;
    }
    • 在非严格模式下,实参对象的数组元素是函数形参所对应实参的别名,可以通过实参对象修改实参的值。
    • 在非严格模式下,arguments仅仅是一个标识符,在严格模式中,它变成了一个保留字。严格模式中的函数无法使用arguments作为形参名或局部变量名,也不能给arguments赋值。
    function f(x) {
        console.log(x);         // 输出实参的初始值
        arguments[0] = null;    // 修改实参数组元素同样会修改x的值
        console.log(x);         // 输出"null"
    }

    callee和caller属性

    • 除了数组元素,实参对象还定义了callee和caller属性。
    • callee属性指代当前正在执行的函数。caller指代调用当前正在执行的函数的函数。

    在匿名函数中,可通过callee来递归地调用自身:

    var factorial = function(x) {
        if (x <= 1) return 1;
        return x * arguments.callee(x-1);
    }

    将对象属性用做实参

    当一个函数包含超过3个形参时,对于程序员说,要记住调用函数中实参的正确顺序比较困难。最好通过名/值对的形式来传入参数,这样参数的顺序就无关紧要了。

    function arraycopy(/* array */ from, /* array */ to, /* integer */ length) {
        // 逻辑代码
    }
    
    // 使用对象当参数
    function easycopy(args) {
        arraycopy(args.from, args.to, args.length);
    }
    
    var a = [1,2,3,4], b = [];
    easycopy({ from: a, to: b, length: 4});

    实参类型校验

    JavaScript会在必要的时候进行类型转换,如果期望的实参是一个字符串,那么实参值无论是原始值还是对象都可以很容易地转换成字符串。但是当期望的实参是一个数组时,就无法对非数组对象进行转换了,所以有必要对实参进行校验。

    function flexisum(a) {
        var total = 0;
        for (var i=0; i < arguments.length; i++) {
            var element = arguments[i], n;
    
            if (element == null) continue;          // 忽略null和undefined实参
            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;
    }

    作为值的函数

    在JavaScript中,函数不仅是一种语法,也是值,也就是说,可以将函数赋值给变量,存储在对象的属性或数组的元素中,作为参数传入另外一个函数等。

    // 声明一个函数
    function square(x) { return x*x; }
    
    var s = square;         // 赋值给变量
    s(4);                   // => 16
    
    var o = { m: square };  // 赋值给对象
    o.m(5);                 // => 25
    
    var a = [square, 6];    // 赋值给数组
    a[0](a[1]);             // => 36

    自定义函数的属性

    函数是一种特殊的对象,所以可以为函数定义属性以完成特殊的需求。 
    可以通过函数属性实现”静态”变量的需求。

    // 由于函数声明被提前了,因此可以在声明之前赋值
    uniqueInteger.counter = 0;
    
    function uniqueInteger() {
        return uniqueInteger.counter++;
    }
    
    var a = uniqueInteger();    // 0
    a = uniqueInteger();        // 1
    a = uniqueInteger();        // 2

    作为命名空间的函数

    不在任何函数内声明的变量是全局变量,在整个JavaScript程序中都是可见的。基于这个原因,我们常常简单地定义一个函数用做临时的命名空间,在这个命名空间内定义的变量都不会污染到全局命名空间。

    function mymodule() {
        // 这个模块所使用的所有变量都是局部变量
        // 而不会污染全局命名空间
    }

    闭包

    JavaScript也采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是调用时决定的。为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还包含函数定义时的作用域链。 
    函数体内部的变量都可以保存在函数作用域内,看起来是函数将变量”包裹”起来了,这种特性称为”闭包”。

    var scope = "global scope";
    function checkscope() {
        var scope = "local scope";
    
        // 定义时使用局部变量
        function f() { return scope; }
    
        return f(); // 返回函数的调用结果
    }
    
    checkscope();   // => "local scope"

    如果更改下checkscope定义,将返回值更改为函数定义,如下:

    var scope = "global scope";
    function checkscope() {
        var scope = "local scope";
    
        // 定义时使用局部变量
        function f() { return scope; }
    
        return f;   // 返回函数的定义
    }
    
    checkscope()();   // => "local scope"

    虽然调用函数的作用域变了,但是函数的输出结果依然不变,因为函数保存了自己的作用域链。 
    通过闭包,可以实现一个更好的计数器类,将变量包裹起来:

    function counter() {
        var n = 0;
        return {
            count: function() { return n++; }
            reset: function() { n = 0; }
        };
    }
    
    var c = counter();
    c.count();          // => 0
    c.count();          // => 1
    c.reset();          // => 0

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

    length属性

    arguments.length表示传入函数的实参的个数。函数的length属性表示函数形参的个数,这个属性是只读的。 
    可以通过这个属性对函数的参数个数进行校验:

    function check(args) {
        var actual   = args.length;
        var expected = args.callee.length;  // 形参个数
        if (actual != expected) {
            throw Error("Expected " + expected + "args; got " + actual);
        }
    }

    prototype属性

    这个属性指向一个对象的引用,这个对象称做”原型对象”。每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。

    call()和apply()方法

    这2个方法属于函数的方法属性,在前面已经有介绍,不再重复介绍。

    bind()方法

    bind()是在ECMAScript5中新增的方法,这个方法的主要作用就是返回一个新的函数,这个函数将bind的对象作为调用上下文

    function f(y) { return this.x + y; }
    var o = { x: 1 };
    var g = f.bind(o);  // bind返回一个函数
    g(2);               // =>1,以对象o作为调用上下文执行f(y)

    可以通过如下代码来实现简单的bind():

    function bind(f, o) {
        if (f.bind) return f.bind(o);
        else return function() {
            // 此处的arguments为调用bind返回函数时传递的参数
            // 上例中为2(g(2))
            return f.apply(o, arguments);   
        }
    }

    但ECMAScript5中的bind()方法不仅仅是将函数绑定至一个对象,它还能将实参也绑定至this,这种编程技术, 有时被称为”柯里化“(currying)。参照下面的例子:

    function f(y,z) { return this.x + y + z; };
    var g = f.bind({x: 1}, 2);  // 绑定this和y
    g(3);                       // =>6,this.x绑定到1,y绑定到2,z绑定到3

    下面的代码给出了更标准的bind()方法,将这个方法另存为Function.prototye.bind:

    if( !Function.prototye.bind) {
        Function.prototye.bind = function(o /*, args */) {
            // 将this和arguments的值保存在变量中
            // 以便在后面嵌套的函数中使用
            var self = this, boundArgs = arguments;
    
            // bind()返回一个函数
            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]);
    
                // 以绑定对象o作为上下文来调用函数self
                // 并传递所有的实参args
                return self.apply(o, args);
            };
        };
    }

    ECMAScript5定义的bind()方法有一些特性是上述代码无法模拟的:

    • 真正的bind()方法返回的函数,length属性是绑定函数的形参个数减去绑定的实参个数。
    • 真正的bind()方法可以创建构造函数,如果bind()返回的函数用做构造函数,将忽略bind()传入的this,但是实参会正常绑定。
    • 由bind()方法返回的函数并不包含prototype属性,如果返回函数用做构造函数,则创建的对象从原始的未绑定构造函数中继承prototype,同时,使用instanceof运算符,绑定构造函数和未绑定构造函数并无两样。
    function Point(x, y) {
      this.x = x;
      this.y = y;
    }
    
    Point.prototype.toString = function() { 
      return this.x + ',' + this.y; 
    };
    
    var p = new Point(1, 2);
    p.toString(); // '1,2'
    
    var emptyObj = {};
    var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
    // 以下这行代码在 polyfill 不支持,
    // 在原生的bind方法运行没问题:
    //(译注:polyfill的bind方法如果加上把bind的第一个参数,即新绑定的this执行Object()来包装为对象,Object(null)则是{},那么也可以支持)
    // var YAxisPoint = Point.bind(null, 0/*x*/);
    
    // 绑定函数的length属性 = 形参个数 - 绑定实参个数
    console.log(YAxisPoint.length);                         // =>1,(2-1)
    
    // 绑定函数不包含prototype属性
    console.log(Point.prototype);                           // "Point { toString-function()}"
    console.log(YAxisPoint.prototype);                      // undefined
    
    // 绑定函数用做构造函数
    // this指代新创建的对象
    var axisPointA = new YAxisPoint();
    console.log(axisPointA.toString());                     // '0,undefined'
    
    var axisPoint = new YAxisPoint(5);
    console.log(axisPoint.toString());                      // '0,5'
    
    // 使用instanceof时,绑定构造函数和未绑定构造函数并无两样
    console.log(axisPoint instanceof Point);                // true
    console.log(axisPoint instanceof YAxisPoint);           // true
    console.log(new Point(17, 42) instanceof YAxisPoint);   // true

    toString()方法

    和所有的JavaScript对象一样,函数也有toString()方法。实际上,大多数(非全部)的toString()方法的实现都返回函数的完整源码。内置函数往往返回一个类似”[native code]”的字符串。

    Function()构造函数

    前面已经介绍,函数可以通过定义语句或直接量表达式来定义。函数还可以通过Function()构造函数来定义。 
    Function()构造函数可以传入任意数量的实参,最后一个实参表示的是函数体。 
    Function()构造函数并不需要通过传入实参以指定函数名。Function()会构造一个匿名函数。

    var f = new Function("x", "y", "return x*y;");
    
    // 这个定义与下面的函数定义等价
    var f = function(x, y) { return x*y; }

    Function()构造函数有以下几个特点:

    • Function()在运行时动态地创建并编译函数。
    • 每次调用Function()构造函数都会解析函数体,并创建新的函数对象。
    • Function()构造函数创建的函数并不使用词法作用域,函数体代码的编译总是在顶层函数执行
    var scope = "global";
    function constructFunction() {
        var scope = "local";
    
        return new Function("return scope");    // 无法捕获局部作用域
    }
    
    constructFunction()();                      // => "global"

    可调用对象(callable object)

    可调用对象是一个对象,可以在函数调用表达式中调用这个对象。所有的函数都是可调用的,但并非所有的可调用对象都是函数。

    • IE8之前的版本实现了客户端方法(诸如window.alert()和Document.getElementsById()),使用了可调用的宿主对象,而不是内置函数对象。IE9将它们实现为真正的函数,因此此类可调用的对象越来越罕见。
    • 另外一个常见的可调用对象是RegExp对象,但代码最好不要对可调用的RegExp对象有太多依赖,对RegExp执行typeof运算的结果并不统一,在有些浏览器中返回”function”,在有些返回”object”。

    检测一个对象是否是真正的函数对象:

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

    函数式编程

    和Lisp、Haskell不同,JavaScript并非函数式编程语言,但可以像操控对象一样操控函数,也就是说,JavaScript中可以应用函数式编程技术

    使用函数处理数组

    使用函数式编程,简洁地实现计算平均值、标准差:

    // 首先定义2个函数对象
    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));

    高阶函数(higher-order function)

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

    function mapper(f) {
        return function(a) { return a.map(f); } // 注意: 此处没有对参数a进行数组验证
    }
    
    var increment   = function(x) { return x+1; }
    var incrementer = mapper(increment);
    incrementer([1,2,3]);   // => [2,3,4]

    不完全函数(partial function)

    不完全函数是一种函数变换技巧,即把一次完整的函数调用拆成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数,每次函数调用叫做不完全调用(partial application)。

    // 实现一个工具函数,将类数组对象转换为真正的数组
    function array(a, n) { return Array.prototye.slice.call(a, n || 0); }
    
    // 将第1次调用的实参放在左侧
    function partialLeft(f /* , ... */ ) {
        var args = arguments;
        return function() {
            var a = array(args, 1);          // 获取第1个参数之后所有的实参
            a = a.concat(array(arguments));
            return f.apply(this, a);
        };
    }
    
    // 将第1次调用的实参放在右侧
    function partialRight(f /* , ... */ ) {
        var args = arguments;
        return function() {
            var a = array(arguments);
            a = a.concat(array(args, 1));
            return f.apply(this, a);
        };
    }
    
    
    // 将第1次调用实参中的undefined值替换成第2次调用的实参
    function partial(f /* , ... */ ) {
        var args = arguments;
        return function() {
            var a = array(args, 1);
            var i = 0, j = 0;
            for(; i < a.length; i++) {
                if(a[i] === undefined) a[i] = arguments[j++];
            }
            a = a.concat(array(arguments, j));
            return f.apply(this, a);
        };
    }
    
    // 这个函数带有3个参数
    var f = function(x, y, z) { return x * (y - z); };
    
    partialLeft(f, 2)(3, 4);        // => -2  [2 * (3 - 4)]
    partialRight(f, 2)(3, 4);       // => 6;  [3 * (4 -2)]
    partial(f, undefined)(3, 4);    // => -6; [3 * (2 - 4)]

    记忆(memorization)

    记忆只是一种编程技巧,本质上是以空间换时间,在客户端代码中,执行时间往往成为瓶颈,因此这种做法是非常可取的。 
    下面定义一个高阶函数,接收一个函数作为实参,并返回带有记忆能力的函数:

    function memorize(f) {
        var cache = {};
    
        return function() {
            var key = arguments.length + Array.prototye.join.call(arguments, ",");
            if(key in cache) 
                return cache[key];
            else
                return cache[key] = f.apply(this, arguments);
        };
    }
    
    // 定义有记忆功能的斐波那契函数
    var factorial = memorize(function(n) {
                                return (n <= 1) ? 1: n * factorial(n-1);
                            });
    factorial(5);   // => 120,同时记忆了4~1的值


    JavaScript 中的indexOf("?")怎么理解?

    indexOf是String对象的一个方法,作用是寻找子串在字符串中的位置,若找到,返回子串首次出现的下标,否则返回-1

    stringObject.indexOf(searchvalue,fromindex)searchvalue:要搜索的子串fromindex:从哪个下标开始找(之前出现的被忽略),

    可选参数注:indexOf区分大小写,并且下标从0开始例:var str="Hello world!"document.write(str.indexOf("Hello") + "<br />");//0document.write(str.indexOf("world") + "<br />");//6document.write(str.indexOf("World") + "<br />");//-1document.write(str.indexOf("o") + "<br />");//4document.write(str.indexOf("o",5) + "<br />");//7

  • 相关阅读:
    数据库课程设计报告学生学籍管理信息系统
    C++ 指针
    解决知乎推荐视频问题
    踩坑指南接口返回前端json数据报错前端无法接收到
    java的接口如何设计异常的理解
    关于webapp项目打war包的问题
    关于继承的一点理解
    hadoop简介
    杨卫华:新浪微博的架构发展历程(转)
    linux server 配置vim编程位置
  • 原文地址:https://www.cnblogs.com/yunfeioliver/p/7747152.html
Copyright © 2020-2023  润新知