本章介绍Function对象,它是JS语言最复杂的内容。

Java语言中没有Function对象,而是普通的方法,它的概念也比较简单,包含方法的重载,重写,方法签名,形参,实参等。

JS语言中的Function对象,它扮演了很多的角色,而且这些角色并不是互斥的。

  • 当Function角色为普通方法时,它包括参数,上下文,返回值,异常处理等内容。是Function的基础。
  • 当Function角色为构造器时,需要深入理解原型链的概念,这是实现继承的基石。
  • 当Function角色为命名空间时,需要深入理解函数的作用域,这是实现封装的基石。
  • 当Function角色为变量时,它可以作为参数,赋值给变量,也可以作为对象的属性。它赋予函数灵活多变的特点。
  • 当Function角色为普通对象时,它也拥有方法,属性。

深入理解这些角色,学习好Function是编写优秀程序的前提。据不完全统计,Function使用率非常高,80%的程序都会用到。

本章的内容可以分为三类知识点

Function对象

  1. 基础:字面量,组成部分,调用方式,参数,上下文,返回值,异常处理。
  2. 概念:prototype,作用域,闭包,添加自定义方法,立即执行函数表达式(本书未提及),模块化。
  3. 思想:递归,回调,级联,切面(curry),备忘(memoization)。

书籍的原始结构如下:

  1. 第一小节介绍Function对象
  2. 第二小节介绍字面量以及Function的组成部分
  3. 第三小节介绍Function的调用方式
  4. 第四小节介绍参数
  5. 第五小节介绍返回值
  6. 第六小节介绍异常处理
  7. 第七小节添加自定义方法
  8. 第八小节介绍递归
  9. 第九小节介绍Function的作用域
  10. 第十小节介绍闭包
  11. 第十一小节介绍模块化
  12. 第十二小节介绍级联(cascade)
  13. 第十四小节介绍代理(curry)
  14. 第十五小节介绍备忘(memoization)

  本章的内容比较长,但也没有办法,毕竟Function是JS语言的基石。

Function对象

Function对象拥有自己的属性和方法,方法包括bind,从Object继承而来的方法,例如toString。

属性包括arguments,caller,callee,prototype。

  • arguments:Function的参数,在参数部分介绍
  • caller:Function的调用对象。
  • callee:Function本身
  • prototype:Function的prototype属性。重点内容。

prototype属性

  每个Function对象的产生都会同时创建一个Prototype对象,可以通过prototype属性访问该对象,Prototype对象只有一个属性constructor,它的值为Function本身。而其他对象创建时,不会创建Prototype对象,也不会拥有prototype属性。

  最容易混淆的概念是Prototype对象与对象原型链之间的关系。在学习之前一直以为它们是同一个对象,但是其实并不是。以下分几种情况来介绍Prototype对象与原型链

  1. 当通过字面量方式创建对象,创建function,
  • obj.prototype属性不存在,返回undefined,
  • Object.getPrototypeOf(obj) === Object.prototype,返回true。

当为function时,

test.prototype属性存在,它是Prototype对象,该对象只有一个属性constructor,指向自身。

  • Object.getPrototypeOf(test) === Function.prototype,返回true。
  • Object.getPrototypeOf(test) === test.prototype,返回false。

  2.当通过new方式创建对象,假定此时test为构造器函数。

Obj.prototype属性不存在,返回undefined

Object.getPrototypeOf(obj) === test.prototype,返回true。

可以看到对象Object无论使用什么方式创建,它都是不存在prototype属性的,而Function对象总是存在prototype属性,该属性值为对象,该对象只有一个属性constructor,值为Function自身。

  当通过构造器test创建对象时,它的原型链对象为test.prototype。它的prototype属性还是undefined。

  代码示例如下:

function test() {
/**
* 创建test函数时,同时创建prototype对象,该对象拥有属性constructor
* 值为test
*/
console.log(test.prototype);
// 返回true
console.log(test.prototype.constructor === test);
// 创建对象时,不会创建prototype对象,它的值为undefined
var obj = {};
// 返回undefined
console.log(obj.prototype);
}

访问属性

参考对象的访问属性过程,上述对象的原型链如下

  • 字面量对象的原型链:obj---->Object.prototype---->null
  • 构造器方法的原型链:obj--->test.prototype---->Object.prototype---->null
  • Function的原型链:test----->Function.prototype----->Object.prototype--->null

组成部分

定义函数的语法格式为:function name(args) { return expression;}

  1. 第一部分为function关键字,它是固定的,而且是必须存在的。在ES6中新语法中不在存在这些约束。
  2. 第二部分为func的名称,当function为变量时,func的名称是可选的,如果不存在,为变量的名称,属性的名称。当function为构造器时,func的名称首字母必须大写,它是一种约定俗成的规则,不是大写也是可以的。当function为其他类型时,func的名称是必须存在的。
  3. 第三部分为参数,JS中不限定参数的类型和个数。
  4. 第四部分为函数体,由0到N条的statements语句组成,如果有返回值,必须存在return语句,此时expression必须与return在同一行,如果无返回值,有两种情况,一种是有return语句无expression,另外一种是根本无return语句。

调用方式

书中介绍的Function调用方式有四种。个人理解它的调用方式与它扮演的角色有关。

  • 当Function扮演的角色是构造器时,此时调用方式必须通过new关键字,不使用new关键也可以调用构造器,但是this关键字会指向全局对象,而不是构造器的新对象,这会导致错误。
  • 当Function扮演的角色是变量时,此时调用的方式为obj.func(),当obj为全局对象时,可以省略。
  • 当Function扮演的角色是普通方法时,可以直接通过func调用。
  • 任何的func都是Function对象的实例,都适用于Function.prototype中的apply,call方法。

参数

JS的参数不限制参数的类型和个数。在编写参数时,通常会考虑以下几种情形。

保证参数是必填的,处理方式有两种

  1. 若参数没有传入,直接抛出异常
  2. 若参数没有传入,设置默认值,在ES6的新语法中已支持。

保证参数的类型,处理方式有

  1. 判断基本数据类型,使用typeof,判断数组使用isArray,判断Function,使用$.isFunction,判断对象,使用class属性。

  保证参数的个数,当入参少于参数个数时,多余参数的值设置为undefined,当入参大于参数个数时,多余的入参被忽略。处理方式有

  1. 使用arguments对象,它本质是一个”array-like”对象,length为入参的个数,属性名为入参的索引值,属性值为入参的值。

可变长参数,处理方式有

  1. ES6的新语法,三个dot号,和Java的语法相同
  2. 将变量封装为对象,动态的给对象添加属性。

返回值

Func的返回值比较简单。

  • 当有返回值时,必定存在return语句,return后面的值为函数的返回值,它可以是任意的对象,数组,Function,表达式等等。
  • 当无返回值时,存在两种情形,一种是有return语句,无value和expression,另外一种是无return语句。

异常处理

Func的异常处理使用try catch。Catch的入参为Object,至少包含name或type属性,描述异常的类型,message,描述异常的信息。

添加自定义方法

JS的对象是基于原型链,添加自定义方法,本质是给原型链添加自定义方法。格式有以下几种。

  • 将prototype重新指向一个新对象,并将constructor属性设置为Function
  • 在原有的prototype上添加方法
  • 使用extend方法,格式为$.extends(prototype对象,newObj);
function Person(name,age){
this.name = name;
this.age = age;
} // 第一种方式,指向新对象
Person.prototype = {
constructor:Person,
method1:function(){},
method2:function(){}
} // 第二种方式,利用原有的prototype
Person.prototype.method3 = function () { };
Person.prototype.method4 = function () { } // 第三种方式,extends方法
$.extend(Person.prototype,{
method5:function(){ }
});

递归

本小节通过JSON组织树形结构的数据,并演示如何遍历该树形结构。

数据

  它的代码为:

var node = {
name: "A",
children: [{
name: "B",
children: [{
name: "D"
}, {
name: "E"
}]
}, {
name: "C",
children: [{
name: "F"
}, {
name: "G"
}]
}]
};

格式为:

中序遍历

/**
* 中序遍历节点,它的基准条件为节点没有children属性
* @param node
*/
function walkTree(node) {
if (node.children === undefined) {
console.log(node.name);
} else {
var left = node.children[0];
var right = node.children[1];
// 中序遍历的写法
walkTree(left);
console.log(node.name);
walkTree(right);
}
}}
// 执行结果为DBEAFCG
walkTree(node);

后序遍历

它的执行结果为DEBFGCA

walkTree(left);
walkTree(right);
console.log(node.name);

前序遍历

它的执行结果为ABDECFG

console.log(node.name);
walkTree(left);
walkTree(right);

作用域

函数的作用域是函数扮演命名空间角色的基石,JS语言没有代码块作用域的概念,在ES6中已提供。下面分析函数的作用域

  • 当无嵌套函数时,函数中定义的所有变量都会提升到最开始,在定义之前访问变量时,值为undefined如果函数外部存在同名变量时,外部的同名变量被忽略。
  • 当存在嵌套函数时,嵌套函数可以访问外部函数所有定义的变量,但是this除外,每一个函数都有自己独立的this和arguments对象。
function outerFunc() {
var a = 3, b = 5;
var outerThis = this;
function innerFunc() {
var b = 7; // 在outerFunc中存在同名的变量,outerFunc中的b被忽略。
var c = 11;
var innerThis = this;
// 此时a的值为21, innerFunc中不存在同名变量,实际是在修改outerFunc中的a
a += b + c;
// 返回true,都指向全局对象windows
console.log(outerThis === innerThis);
}
innerFunc();
// 此时的b为outerFunc中定义的变量,innerFunc中的同名变量不可见,a的值为21,b的值为5
console.log(a, b);
}

闭包

  闭包的概念是指函数调用阶段还可以访问函数定义阶段的上下文。

这句话个人理解的意思是,在函数定义时,会同时创建一个prototype对象,在函数调用时,所有函数都公共拥有这个prototype对象,虽然每次调用会创建新的arguments对象,但是不会再次prototype对象。

function testClosure()
{
var proto = testClosure.prototype;
var that = this;
var args = arguments;
return {
proto:proto,
that:that,
args:args
}
}
var result1 = testClosure("a","b","c");
var result2 = testClosure.apply("hello World",["a","b","c"]);
// 返回true
console.log(result1.proto === result2.proto);
// 返回false
console.log(result1.that === result2.that);
// 返回false
console.log(result1.args === result2.args);

回调

当文档加载完成之后,JS执行脚本的方式是由事件驱动的,假设同时触发多个事件,由于JS是单线程的,所以无法同时执行多个事件,只能将这些事件加入到队列当中依次执行。

假设事件中存在比较耗时的操作,例如在事件1中存在网络请求,而服务器很久没有响应,此时事件1一直处于等待状态,而整个队列都在等待事件1,整个页面陷入”假死”状态,无法响应新事件。

必须存在一种机制,使得事件1不阻塞整个队列,而当事件1得到服务器响应之后,告知主线程它已经准备就绪。

函数的回调,是指事件在处理到某个阶段时会触发的函数调用。假设事件的处理函数为func1,而被触发的函数调用为method1,那么method1为func1的回调。

  回调的好处是:

  1. 保证了JS主线程不会被耗时的事件阻塞。
  2. 回调函数与事件处理器之间形成了是一种事件触发机制,允许用户指定回调函数。例如document,window的onload,ajax的success。

模块化

  略,在第五章节介绍

级联

函数的级联概念比较简单,是指函数的返回值为调用者本身,达到连续调用方法的目的。在编写脚本时比较容易实现,将this作为返回值即可。

级联只出现在Function角色为变量,而且Function作为属性值时才有意义。当Function扮演其他角色时,无任何意义。

最常见的级联是jQuery。例如$(“#id”).attr(“href”,”www.baidu.com”).css().text(“百度”);

curry

函数的curry概念没有理解,查询过资料,总感觉有点像Java的方法代理,即method--->proxyMethod,proxyMethod内部调用method。

/**
* 原始方法
* @param a
* @param b
* @param c
*/
function method(a, b, c) {
console.log(a);
console.log(b);
console.log(c);
return "method1的返回值";
}
/**
* curry方法,它的返回值为代理方法,将method的参数传递给proxyMethod
* @param args
* @returns {function(): void}
*/
function curry(args) {
// 额外的功能
return function () {
return method.apply(null, args);
}
}
// 代理方法
var proxyMethod = curry(["a", "b", "c"]);

  method的返回值为proxyMethod的返回值 它们之间的关系

  1. curry的参数为method的参数
  2. curry的返回值为proxyMethod
  3. proxyMethod调用了method,并可以访问curry和method的参数。

备忘(memorization)

  备忘的作用是在递归时通过缓存中间结果,减少了函数的调用。

/**
* 计算数字的阶层
* @param n
*/
function fibonacci(n) {
console.log("调用了函数");
if (n === 0 || n === 1) {
return n;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
} /**
* 计算数字的阶层,用result缓存结果,所以调用次数大大减少
* @param n
* @param result
* @returns {*}
*/
function cacheFibonacci(n, result) {
if (!result) result = {};
console.log("调用了函数");
if (n === 0 || n === 1) {
result[n] = n;
return result[n] === undefined ? n : result[n];
} else {
if (result[n - 1] === undefined) {
result[n - 1] = cacheFibonacci(n - 1, result);
}
if (result[n - 2] === undefined) {
result[n - 2] = cacheFibonacci(n - 2, result);
}
return result[n - 1] + result[n - 2];
}
}

  至此本篇内容结束

前端——语言——Core JS——《The good part》读书笔记——第四章节(Function)的更多相关文章

  1. 前端——语言——Core JS——《The good part》读书笔记——初篇

    本书是一本经典,优秀的JS书籍. 目的 在书籍中作者多次提及本书的目的,让读者去发现语言中的精华部分,避免糟粕部分,提高代码的编写质量.简述为取其精华去其糟粕. 本书的内容只涉及到Core JS部分, ...

  2. 前端——语言——Core JS——《The good part》读书笔记——第九,十章节(Style,Good Features)

    第九章节 本章节不再介绍知识点,而是作者在提倡大家培养良好的编码习惯,使用Good parts of JS,避免Bad parts of JS.它是一篇文章. 本文的1-3段阐述应用在开发过程中总会遇 ...

  3. 前端——语言——Core JS——《The good part》读书笔记——第一章节(Good Parts)

    本章是引言,有四个小节,具体内容如下: 第一小节 第一小节介绍作者的观点,作者编写本书的目的. 原文:I discovered that I could be a better programmer ...

  4. 前端——语言——Core JS——《The good part》读书笔记——第五章节(Inheritance)

    本章题目是继承,实质上介绍JS如何实现面向对象的三大特性,封装,继承,多态.本章的最后一个小节介绍事件. 与Java语言对比,虽然名称同样称为类,对象,但是显然它们的含义存在一些细微的差异,而且实现三 ...

  5. 前端——语言——Core JS——《The good part》读书笔记——第七章节(正则)

    本章介绍正则表达式的内容.正则表达式是一门独立的语言,它拥有自己的语法规则,在学习本章之前需要了解基本的语法规则. 正则表达式是通用的,意味着同样的语法规则可以适用于不同的编程语言,相同的正则表达式在 ...

  6. 前端——语言——Core JS——《The good part》读书笔记——第三章节(Object)

    本章介绍对象. 在学习Java时,对象理解为公共事物的抽象,实例为具体的个例,对象为抽象的概念,例如人为抽象的概念,具体的个例为张三,李四. Java对象种类多,包含普通类,JavaBean,注解,枚 ...

  7. 前端——语言——Core JS——《The good part》读书笔记——第六章节(Arrays)

    本章介绍数组的内容,Java中的数组在创建时,会分配同等大小的内存空间,一旦创建数组的大小无法改变,如果数据超过数组大小,会进行扩容操作.并且数组的元素类型在创建时必须是已知的,而且只能存放相同数据类 ...

  8. 前端——语言——Core JS——《The good part》读书笔记——第八章节(Methods)

    本章介绍JS核心对象的方法.这些对象包括Array,Function,Number,Object,RegExp,String.除这些常用的核心对象还有Date,JSON. 本章更偏向于API文档,介绍 ...

  9. 前端——语言——Core JS——《The good part》读书笔记——附录三,四,五(JSLint,铁路图,JSON)

    1.JSLint 本书的JSLint部分只是一个引言,详细了解该工具的使用参考http://www.jslint.com/ 2.铁路图 在本书中使用过的铁路图集中放在这部分附录中,其实读完本书之后,没 ...

随机推荐

  1. windows环境下安装JDK

    一.环境准备 Windows10 jdk-9.0.1 二.下载并安装JDK 到Java的官网下载JDK安装包,地址:http://www.oracle.com/technetwork/java/jav ...

  2. PHP返回json数据为null

    文件编码非utf-8,导致json_encode()返回false:最后前台ajax接收不到数据

  3. C语言链表的中间结点

    给定一个带有头结点 head 的非空单链表,返回链表的中间结点. 如果有两个中间结点,则返回第二个中间结点. 示例 1: 输入:[1,2,3,4,5]输出:此列表中的结点 3 (序列化形式:[3,4, ...

  4. docker镜像相关的常用操作

    1.保存镜像 #docker save 镜像名称 -o 保存的完整地址和文件名 docker save zhoushiya/zhiboyuan -o d:/zhiboyuan.tar 2.载入镜像 # ...

  5. 高通量计算框架HTCondor(六)——拾遗

    目录 1. 正文 1.1. 一些问题 1.2. 使用建议 2. 相关 1. 正文 1.1. 一些问题 如果真正要将HTCondor高通量计算产品化还需要很多工作要做,HTCondor并没有GUI界面, ...

  6. 在SQL2005中修改数据库名称

    SQL Server 2005中有个数据库HT_WisdomDataBaseCenter,现在要将其改名为HT_NBSBOneNetcs1步骤:(1) 分离数据库:打开management studi ...

  7. 编码 - 坑 - win10 下采用 utf-8, 导致 gitbash 中文字体异常, 待解决

    blog01 概述 使用 git 中, 遇到一个坑 背景 最近遇到一个 编码转换 问题 本来也 一知半解 要是有人能给我讲讲就好了 环境 win10 1903 git 2.20.1 1. 问题 概述 ...

  8. 部署prerender服务器

    // 安装 git sudo apt-get install git sudo apt-get install curl // 请先确认服务器是否安装了curl 如果已经安装跳过即可 // 安装 no ...

  9. [WC2006] 水管局长 - Link Cut Tree

    离线后逆序处理所有操作,那么就变成了加边询问,根据MST的性质,显然维护MST询问链上max即可 #include <bits/stdc++.h> using namespace std; ...

  10. (转)git学习教程

    转自:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000