Javascript语言精粹学习笔记之函数

一、引入

    所有的过失在未犯以前,都已定下应处的惩罚:
    威廉●莎士比亚,《一报还一报(Measure for Measure)》

  • JavaScript中最好的特性就是它对函数的实现。它几乎无所不能。 但是,想必你也能预料到,函数在JavaScript里也并非万能药。
  • 函数包含一组语句,它们是JavaScript 的基础模块单元, 用于代码复用、信息隐藏和组合调用。函数用于指定对象的行为。 一般来说,所谓编程就是将一组需求分解成一组函数与数据结构的技能。

二、函数

2.1 函数对象

  • 函数用于①代码复用②信息隐藏③组合调用。一般来说,所谓编程,就是将一组需求分节成一组函数与数据结构的技能。
  • JavaScript的函数就是对象。函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype)。
  • 每个函数在创建时会附加两个隐藏属性,函数的上下文和实现函数行为的代码。
  • 每个函数对象在创建时也随配有一个prototype属性。它的值是一个拥有constructor属性且值为该函数的对象。
  • 因为函数是对象。所以它们可以像任何其他的值一样被使用。函数可以存放在变量、对象
    和数组中,函数可以被当作参数传递给其他函数,函数也可以再返回函效。而且,因为函
    数是对象,所以函数可以拥有方法。

2.2 函数字面量

  • 函数字面量包括4个部分。①保留字function②函数名,可以省略,③一组参数④一组语句。
var sum= function (a, b) {
            return a + b
        }
  • 函数字面量可以出现在任何允许表达式出现的地方。一个内部函数除了可以访问自己的参数和变量,同时也可以自由访问把它嵌套在其中的父函数的参数和变量。通过函数字面量创建的函数对象包含一个连接到外部上下文的连接。这被称为闭包

2.3 调用

  • 除了声明时定义的形式参数,每一个函数还接收两个附加的参数:this和argument。
  • 调用运算符是跟在任何产生一个函数值的表达式之后的一对圆括号。圆括号内可包含零个
    或多个用逗号隔开的表达式。每个表达式产生一个参数值。每个参数值被赋予函数声明时
    定义的形式参数名。当实际参数(arguments) 的个数与形式参数(parameters) 的个数不
    匹配时不会导致运行时错误。如果实际参数值过多了,超出的参数值将被忽略。如果实际
    参数值过少,缺失的值将会被替换为unde fined。对参数值不会进行类型检查:任何类型
    的值都可以被传递给参数。
  • JavaScript中一共有四种调用模式。①方法调用模式,②函数调用模式③构造器调用模式④apply调用模式。这些模式在如何初始化关键参数this上存在差异(this指向问题一直困扰很多人。我一般是这样记的,谁调用this就指向谁。)

方法调用模式
  对象的方法执行,this指向该对象。比如:

var obj ={
            value:1,
            showValue:function(){
                console.log('value:',this.value);
            }
        }
        obj.showValue()// value:1

函数调用模式
  当函数以此模式调用时,this 被绑定到全局对象。这是语言设计上的一个错误。倘若语言
设计正确,当内部函数被调用时,this 应该仍然绑定到外部函数的this 变量。这个设计错误的后果是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。

var add = function (a, b) {
            return a + b;
        }
        add(3, 4);  //7
        window.add(3, 4);  //7
        // 这种this被绑定到全局对象(window)。
        // 可以理解是window.add(3,4);

  有种简单的办法就是var that = this;把this存储下。
例:

var myObj = {
            value: 0,
            age: 20,
            showValue: function () {
                console.log('value:', this.value);
                var that = this;
                var showAge = function () {
                    // window上没有age,所以是undefined
                    console.log('这里的this是window ---age:', this.age);  // undefined
                    console.log('age:', that.age);  // 20
                }
                showAge();
            }
        }
        myObj.showValue();  // value: 0 这里的this是window ---age: undefined age: 20

构造器调用模式
  JavaScript是一门基于原型继承的语言。
  如果在一个函数前面带上new 来调用。那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到那个新对象上。
  new 前缀也会改变return 语句的行为。
例:

//创建一个名为Quo的构造器函数。它构造带有status属性的对象
var Quo = function (string) {
            this.status = string;
        }
//给Quo的所有实例提供一个名为get_status的公共方法
        Quo.prototype.get_status = function () {
            return this.status;
        }
//构建一个Quo实例
        var myQuo = new Quo('confused'); // 'confused'

  一个函数,如果创建的目的就是希望结合new 前缀来调用。那么它就被称为构造器函数。按照约定,它们保存在以大写函数命名的变量里。如果调用构造器函数时没有在前面加上new,可能会发生非常糟糕的事情,既没有编译时的警告,也没有运行时广告,所以大写约定非常重要。
  作者不推荐这种形式的构造器函数。有更好的替代方式。
Apply调用模式
  JavaScript是一门函数式的面向对象编程语言,所以对象可以拥有方法。
  apply方法让我们构建一个参数数组传递给调用函数,它也允许我们选择this的值。
  apply方法接收到两个参数。第一个是将被绑定给this的值。第二个就是第一个参数数组。

// apply调用模式
        var array = [3, 4];
        var sum = add.apply(null, array);//sum值为7

        //构造一个包含status成员的对象。

        var statusObject = {
            status: 'A-OK'
        };

        // statusObject 并没有继承来自 Quo.prototype,但我们可以在statusObject 上调
        //用get_status方法,尽管statusObject并没有一个名为get_status的方法。

        var status = Quo.prototype.get_status.apply(statusObject);
        // status 值为'A-OK'

2.4 参数

  • 当函数被调用时,会得到一个“免费”奉送的参数,那就是arguments 数组。通过它函数
    可以访问所有它被调用时传递给它的参数列表,包括那些没有被分配给函数声明时定义的
    形式参数的多余参数。这使得编写一个无须指定参数个数的函数成为可能:
var sum = function () {
            vari, sum = 0;
            for (i = 0; i < arguments.length; i += 1) {
                sum += arguments[i];
            }
            return sum;
        };
        document.writeln(sum(4, 8, 15, 16, 23, 42)); // 108 
  • 因为语言的一个设计错误,arguments 并不是一个真正的数组。它只是一个“类似数组
    (array-like)"的对象。arguments 拥有一个length 属性,但它缺少所有的数组方法。我
    们将在本章结尾看到这个设计错误导致的后果。

2.5 返回

  • return 可用来是函数提前返回。当return 被执行时,函数立即返回而不再执行余下的语句。
  • 一个函数总会返回一个值,如果没指定,那就是返回undefined值。
  • 如果函数调用时在前面加上了new 前缀,且返回值不是一个对象,则返回this(该新对象)。

2.6 异常

  • JavaScript提供了一套异常处理机制。异常是干扰程序的正常流程的非正常(但并非完全是出乎意料)的事故。当查出这样的事故时,你的程序应该抛出一个异常:
var add = function (a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw {
            name: 'TypeError',
            message: 'add needs numbers'
        };
    }
    return a + b;
}

  • throw 语句中断函数的执行。它应该抛出一个exception对象,该对象包含可识别异常类型的name属性和一个描述性的message属性。你也可以添加其他的属性。
  • 该exception对象将被传递到一个try 语句的catch从句;
//构造一个try_it函数,用不正确的方式调用之前的add函数
var try_it = function () {
    try {
        add("seven");
    } catch (e) {
        document.writeln(e.name + ': ' + e.message);
    }
}
try_it(); //TypeError: add needs numbers

  • 如果在try代码块内抛出了一个异常,控制权就会跳转到它的catch从句。
  • 一个try 语句只会有一个将捕获所有异常的 catch 代码块。如果你的处理手段取决于异常的类型,那么异常处理器必须检查异常对象的name属性以确定异常的类型。

2.7 给类型增加方法

  • JavaScript允许给语言的基本类型扩充功能。在第3章中我们已经看到,可以通过Object.prototype添加方法,可以让该方法对所有对象都可用。这样的方式对函数、数组、字符串、数字、正则表达式和布尔值同样适用。
Function.prototype.method = function () {
            this.prototype[name] = func;
            return this;
        }
  • 基本类型的原型是公用结构,所以在类库混用时务必小心。一个保险的做法就是只在确认没有该方法时才添加它。
Function.prototype.methods = function (name, func) {
            if (!this.prototype[name]) {
                this.prototype[name] = func;
            }
            return this;
        }

2.8 递归

  • 递归函数就是会直接或间接地调用自身的一种函数。递归是一种强大的编程技术,递归是用一般的方式去解决每一个子问题。书中举了一个汉诺塔的例子,是程序设计中经典递归问题。详细说明可以参见 百度百科“汉诺塔”词条。
  • 一些语言提供了尾递归优化。尾递归是一种在函数的最后执行调用语句的特殊形式的递归。参见Tail call。 ES6版本扩展了尾递归。参见阮一峰老师的《ES6标准入门》中的尾调用优化

2.9 作用域

  • 在编程语言中,作用域控制着变量与参数的可见性和声明周期。
  • 书中指出当前JavaScript没有块级作用域。因为没有块级作用域,所以最好的做法是在函数体的顶部声明函数中可能用到的所有变量。不过ES6扩展了有块级作用域。

2.10 闭包

  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
    由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
    所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
  • 闭包有两个作用:
    第一个就是可以读取自身函数外部的变量(沿着作用域链寻找)
    第二个就是让这些外部变量始终保存在内存中
<ul class="list">
            <li>0</li>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
// 点击相应节点时,显示对应的序号。可以使用闭包来解决。
        var add_the_handlers = function () {
            var helper = function (i) {
                return function (e) {
                    alert(i);
                }
            }
            var i;
            for (i = 0; i < nodes.length; i += 1) {
                nodes[i].onclick = helper(i);
            }
        }
// 扩展 另外可以用let i = 0,或者把nodes类数组转成数组等方案实现。
// 闭包特性:1、函数内再嵌套函数,2、内部函数可以调用外层的参数和变量,3、参数和变量不会被垃圾回收机制回收。
// 闭包优点 灵活和方便,便于封装。缺点:空间浪费、内存泄露、性能消耗。

2.11 回调

  • 发起异步请求,提供一个当服务器响应到达时随即出发的回调函数。异步函数立即返回,这样客户端就不会被阻塞。

2.12 模块

  • 我们可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。
    举例:给String添加一个deentityify方法。它的任务是寻找字符串中的HTML字符实体并把它们替换成对应的字符。
String.method('deentityify', function () {
            // 字符实体表。它映射字符实体的名字到对应的字符。
            var entity = {
                quot: '"',
                lt: '<',
                gt: '>'
            };
            // 返回 deentityify方法
            return function () {
                return this.replace(/&([^&;]+);)/g,
                    function (a, b) {
                        var r = entity[b];
                        return typeof r === 'string' ? r : a;
                    }
    };
        }());
  • 模块模式利用了函数作用域和闭包来创建被绑定对象与私有成员的关联,在上面例子中,只有deentityify方法有权访问字符实体表这个数据对象。
    模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可以访问的地方。
    使用模块模式就可以摒弃全局变量的使用。它促进了信息隐藏和其他优秀的设计实践。对于应用程序的封装,或者构造其他单例对象,模块模式非常有效。

2.13 级联

  • 有一些方法没有返回值。如果我们让这些方法返回this而不是undefined,就可以启用级联。
  • 在一个级联中,我们可以在单独一条语句中依次调用同一个对象的很多方法。比如jQuery获取元素、操作样式、添加事件、添加动画等。

2.14 套用

  • 函数也是值,从而我们可以用有趣的方式去操作函数值,套用允许我们将函数与传递给它的参数相结合去产生出一个新的函数。
var addl = add.curry(1);
        document.writeln(addl(6)); // 7
  • addl是吧1传递给add函数的curry方法后创建的一个函数。addl函数把传递给它的参数的值加1。JavaScript并没有curry方法,但我们可以通过给Function.prototype添加功能来实现:
Function.method('curry', function () {
            var args = arguments, that = this;
            return function () {
                return that.apply(null, args.concat(arguments));
            };
        });
  • curry方法通过创建一个保存着原始函数和被套用的参数的闭包来工作。它返回另一个函数,该函数被调用时,会返回调用原始函数的结果,并传递调用curry时的参数加上当前调用的参数的所有参数,它使用Array的concat方法去连接两个参数数组。
    糟糕的是,就像我们先前看到的那样,arguments数组并非一个真正的数组,所以它并没有concat方法。要避开这个问题,我们必须在两个arguments数组上都应用数组的slice方法。这样产生出拥有concat方法的常规数组。
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)));
            };
        });

2.15 记忆

  • 函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化称作记忆。
    比如说,我们想要一个递归函数来计算Fibonacci(斐波那契)数列,它的特点是,前面相邻两项之和等于后一项的值。更多参考:斐波那契。最前面两个数字是0和1。
var fibonacci = function() {
    return n < 2? n : fibonacci(n-1) + fibonacci(n-2);
}
  • 这样虽然能完成工作,但它做了很多无谓的工作。
    构造一个带有记忆功能的函数:
var memoizer = function(mome, formula) {
    var recur = function(n) {
        var result = meno[n];
        if (typeof result !== 'number') {
            result = formula(recur, n);
            meno[n] = result;
        }
        return result;
    };
    return recur;
}
  • 再用这个memoizer函数来定义fibonacci函数,提供其初始的memo数组和formula函数。
var fibonacci = memoizer([0,1],function(recur, n){
    return recur(n-1) + recur (n-2);
})
  • 极大的减少了我们的工作量。例如要产生一个记忆的阶乘函数,只需要提供基本的阶乘公式即可:
var factorial = meoizer([1,1], function(recur, n){
    return n * recur(n-1);
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: "JavaScript语言精粹"是一本由Douglas Crockford撰写的著名图书,是许多JavaScript开发者学习与掌握该语言不可或缺的重要资源。 这本书主要关注于JavaScript的核心概念和最佳实践,帮助开发者摆脱一些语言的怪异之处和陷阱,使其能够更加高效地编写可维护和可扩展的代码。 "JavaScript语言精粹"一书将JavaScript的精华提炼为19个章节,每一章节都深入浅出地介绍了一个关键概念,包括函数、对象、数组、代码风格、错误处理等。作者以清晰简洁的语言解释了这些概念的背后原理,并给出了许多实用的代码示例。 这本书不仅适合JavaScript初学者,对于有一定经验的开发者来说也是一本不可多得的参考书。通过阅读它,开发者可以拓宽对JavaScript的理解和认识,提高代码质量和效率。 除了对基础概念的详细介绍外,"JavaScript语言精粹"还包含了一些进阶话题,如闭包、原型链、模块化等,帮助开发者进一步提升其JavaScript编程能力。 总之,"JavaScript语言精粹"是一本权威而实用的JavaScript学习资源,对于想要深入理解和掌握该语言的人来说是极为重要的一本书籍。无论你是初学者还是有经验的开发者,这本书都能帮助你写出更优雅、可读性更高的JavaScript代码。 ### 回答2: 《JavaScript语言精粹》是一本由Douglas Crockford撰写的权威指南,它介绍了JavaScript语言中最重要和最有用的部分。该书通过简洁而清晰的语言讲解了JavaScript的核心概念和特性,为开发者提供了深入理解和运用JavaScript的方法和技巧。 这本书以独特的方式展现了JavaScript的精华,将复杂的语法和概念简化成易于理解和运用的形式。Crockford首先介绍了JavaScript中的基本语法和数据类型,然后深入讲解了函数、对象、原型、闭包等重要概念。他通过具体的示例和练习,引导读者掌握JavaScript中的核心概念和编程技巧。 《JavaScript语言精粹》还包含了对常见错误和陷阱的警示,帮助读者避免在编写JavaScript代码时常见的问题。此外,该书还提供了一些最佳实践和编码规范,帮助开发者写出高质量、可维护的JavaScript代码。 与其他JavaScript教程不同,《JavaScript语言精粹》不仅关注如何正确地使用JavaScript,还强调了一些可以帮助开发者避免错误和提高代码质量的技巧和原则。这使得这本书成为了一本适合初学者和有经验的开发者阅读的权威指南。 总之,《JavaScript语言精粹》是一本深入而全面地介绍JavaScript语言的书籍。无论你是初学者还是有经验的开发者,阅读这本书都可以帮助你建立起对JavaScript的深入理解,并提高你的JavaScript编程技巧。 ### 回答3: 《JavaScript语言精粹》是一本由Douglas Crockford所著的程序设计相关书籍,它主要介绍了JavaScript语言的核心概念和重要知识点。这本书在程序设计领域有着很高的声誉,被许多程序员视为JavaScript编程的经典参考书。 该书的目的是帮助读者深入理解JavaScript语言的精华部分,将复杂的语法和特性解释得简单易懂。它详细介绍了JavaScript的基本数据类型、函数、对象、原型链以及闭包等重要概念,并提供了一些实用的编程技巧和最佳实践。读者通过学习这些内容,可以更好地理解JavaScript的设计哲学和编程范式。 《JavaScript语言精粹》的内容不仅限于语法的讲解,还包括了一些关于代码风格、错误处理和性能优化等方面的建议。它强调代码的可读性、可维护性和可扩展性,帮助读者编写出高质量的JavaScript代码。 这本书的另一个亮点是作者Douglas Crockford的独特见解和深入思考。他不仅仅是介绍了JavaScript的特性,还对其设计和演变进行了深入的分析和比较。他提出了一些有关编程规范和标准化的建议,为读者在实际开发中避免一些常见的陷阱和错误提供了宝贵的经验。 总之,《JavaScript语言精粹》是一本经典的JavaScript编程参考书籍,适合有一定编程基础的读者阅读。它能够帮助读者深入理解JavaScript的核心概念和设计原理,提高编程技巧,写出高质量的代码。无论是初学者还是有经验的开发者,都会从这本书中获益匪浅。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值