文章目录
- 1.1 第一章 初入宗门
- 1.2 第二章 直接量
- 1.3 第三章 数据类型
- 1.4 第四章 数据类型
- 扩展内容:
- 1.5 第五章 基础考核
- 1.6 第六章 何老
- 1.7 第七章 对象数据类型
- 1.8 第八章 对象的取值
- 1.9 第九章 循环遍历的奥妙
- 小结:
- For 循环
- 1.10 第十章 对象内容的遍历
- 1.11 第十一章 外门小比
- 1.12 第十二章 JavaScript 运算符
- 1.13 第十三章 语惊四座
- 1.14 第十四章 秀
- 1.15 第十五章 天秀
- 1.16 第十六章 数组方法
- 1.17 第十七章 蒂花之秀
- 1.18 第十八章 函数七重关之一 (函数定义)
- 1.19 第十九章 JavaScript 编译原理
- 1.20 第二十章 函数七重关之二 (作用域)
- 1.21 第二十一章 函数七重关之三(参数传递)
本书原文作者为 CSDN 博主「剽悍一小兔」https://blog.csdn.net/weixin_39570751 ,是他出版的一本 javascript 编程修仙小说,本文为节选
1.1 第一章 初入宗门
乐阳村处于脚本大陆东部的边缘地带,其民风淳朴,村民日出而作、日落而息。某一日清晨,所有村民都来到了村口,正在为一个十五六岁的少年送行。只见那个少年虽然身体瘦弱,可是目中却绽放出异彩;身躯虽不壮实,倒也挺拔。
“叶小凡,你是我们全村人的骄傲,是百年来唯一具备修行 JavaScript 甲等资质的孩子!马上会有千鹤派的大人来接你,今后你一定要认真修行,给我们全村的人争光!”
他叫叶小凡,几天前通过了修行资质的检验,符合了修行 JavaScript 的资质。
“千鹤派!”叶小凡眼前一亮,语气中伴随着激动,两眼放光。千鹤派在脚本大陆是数一数二的大门派,每个宗门弟子都能够修炼一种神奇的功法——JavaScript,修成之后,其威力之大,足以翻山倒海,称霸一方天地!
不多时,天气风云变化,一道长虹降下,瞬间变为一人。他身穿一件玄青色的玉锦衣服,腰间绑着一根白色蟠螭纹革带,一头黑发,有着一双清澈明亮的眼眸,身形颀长,当真是气宇轩昂、温文尔雅。
“你就是叶小凡?”男子淡淡开口,神识一扫,惊讶地发现此子的修行资质竟为甲等,眼中闪过一丝惊讶。
“在下林元青,千鹤派青山院掌尊,你既然通过了考核,便具备了修炼资质,你随我即刻上山,不得有误。”叶小凡便告别众人,在林元青的术法之下随即化为一道长虹飞天而去,留下了一脸惊讶的村民。
千鹤派分为三大院,分别为青山院、绿水院以及神秘的玄冰院。每个院都有一个掌尊,林元青赫然便是青山院的掌尊!掌尊地位之高,仅次于大长老;大长老之上,又有太上大长老。几乎只是两三次呼吸的时间,林元青已经带着叶小凡来到了千鹤派的青山院。叶小凡两眼一花,
仿佛上一刻还在村口,下一刻就看到了千鹤派的阁楼。
“这里是……?”叶小凡一辈子都没有见过如此宏伟的建筑,忍不住开口。
一想到今后可以在这里修行,更是兴奋,“哈哈,我叶小凡今后一定能突破层层障碍,成为 JavaScript 一代大师!”
“这里是千鹤派的青山院。”林元青招来仆从,给叶小凡随意找了一个住处,又给了一卷功法,吩咐了几句后,似乎还有其他要事,便立即化为一道长虹离去了。
叶小凡拿起功法一看,上面赫然写着“JavaScript 基础修炼要诀”几个银光大字,他眼中放出异彩,一想到村中父老乡亲那期盼的眼神,便下定决心,不混出个名头绝不回去!
1.2 第二章 直接量
叶小凡的住处被安排在青山院西北角的一个房间,虽不宽敞,倒也干净。叶小凡两眼露出振奋的眼神,随便吃了点乡亲们准备的干粮后,就立刻开始打坐修行。编程之修,重在积累,而非资质。资质虽然重要,可是后天的努力更不可缺少。这些道理,叶小凡在还未上山之前就已经熟知!因此,即便是资质平凡,但只要肯下苦功,一样可以修得正果!
叶小凡虽然具有甲等资质,但他依然不骄不躁,开始从《JavaScript 基础修炼要诀》第一页学起。修炼要诀第一章——直接量。编程世界中的直接量,就是表面上可以见到的数据值。常见的直接量有数字、小数、字符串。修行者利用自身体内的能量,凝结出一个个简单的直接量。叶小凡目前的修为较低,连最基本的学徒境界都没到,体内能量薄弱,经过多次尝试,只能凝练出一些简单的数字,比如 10、20。
忽然,叶小凡目光一闪,一个字符串在体内形成!叶小凡细细感悟,原来是一个“Hello World”字符串,叶小凡善于观察,立刻发现了字符串和数字的不同。
字符串的出现必然带着双引号,被很好地包裹住,而数字则是光秃秃的,如 10 或者 20,没有双引号。
“原来,字符串一定需要用双引号包裹,那么单引号是否可行呢?”叶小凡重新运气,转眼间,一个用单引号包裹的‘Hello World’就出现了。见此,叶小凡大喜,哈哈,原来单引号也可以。
1 "Hello World"
2 'Hello World'
突然,这两个字符串和数字像是失去了依托,瞬间化为虚无。叶小凡一惊,心道:看来直接创造出来的直接量只是昙花一现,无法持久存在,要是有一个什么东西能把直接量装起来就好了。
1.3 第三章 数据类型
叶小凡收起心神,继续阅读《JavaScript 基础修炼要诀》,忽然间,他眼前一亮。
“原来如此,直接量虽然只是昙花一现,但是如果能用 var 定义一个变量,再将它指向那个直接量,就能有保存数据的妙用了!”
想到这里,叶小凡立即催动功法,定义了一个变量。
var a;
“成了!”,叶小凡开心地一拍手,心念一动,一个用双引号包裹的“Hello World”字符串凭空出现。这一次,没等字符串遁入虚无,叶小凡就立刻让变量 a 指向了这个字符串。
var a;
a = "Hello World";
“嗯,有点麻烦,还不如直接合并为一句,先定义变量,然后指向一个字符串,这种操作分成了两步,还是一步到位更好。”就在叶小凡这么想的时候,代码立刻发生了变化。
var a = "Hello World";
原来这样也可以!
下一章我们继续学习“数据类型”,学习了数据类型才能更深一步的了解 JavaScript!
1.4 第四章 数据类型
修行还在继续,随着对要诀理解的深入,叶小凡明白,在编程世界中,刚才的直接量都属于一种数据,如同人有男女之分一样,数据也是有类型的。
在 JavaScript 中,数据可分为两类,分别为原生数据类型(primitive type)和对象数据类型(object type)。
叶小凡心念一动,数字“6”立刻浮现在他体内的内存元海中,同时,为了不让数字消失,他还专门定义了一个变量指向这个数字。
var num = 6;
叶小凡心中已有明悟,这个数字和字符串都属于原生数据类型。那么,还有其他原生数据类型吗?随着心念扫过要诀,一炷香的时间后,
叶小凡已经若有所悟。原来,原生数据类型包括数字、字符串、布尔值,还有两个特殊的类型:null 和 undefined。
“布尔值,”叶小凡口中喃喃,“它是一种只有 true 和 false 两种状态的类型。”嗯,就好像以前自己在村子里点蜡烛,要么蜡烛亮起来,要么蜡烛熄灭,只有这两种状态。至于 null 和 undefined,叶小凡略一皱眉,结合秘籍,开始了感悟。
一炷香的时间过去了,叶小凡猛地睁眼,呼吸都有点急促。
“我明白了!从用法上来看,null 和 undefined 都代表了直接量的空缺,如果一个变量指向了其中任何一个,都代表 false 的含义,也表示没有或空的概念。
而从根本意义上讲,undefined 要比 null 更加严重一点,代表本不应该出现的错误,比如我刚才定义了一个变量 a,但是我没有把任何直接量赋给它,那么 a 就默认指向了 undefined;而 null 不同,有的时候,我需要给某些变量赋值 null,以达到清空的目的。”
扩展内容:
JavaScript 包括直接量和变量。首先说直接量,什么是直接量呢?在 JavaScript 的世界里,直接量包含数值(如 10/20)、逻辑值(true/false)、字符串(如“nihao”)、null、undefined、对象和函数。
其中,函数也称方法,对象和函数会在之后的章节中慢慢介绍。你暂时可以认为对象是存放数据的一个容器,而函数是应用程序处理某一系列逻辑的一个过程设计。
null 是一个特殊的关键字,表示没有值;null 也是一个原始值,因为 JavaScript 是大小写敏感的,所以 null 和 Null、NULL 或者其他变量是有区别的。
undefined 是一个顶级属性,它代表某一个变量未定义。同样,undefined 也是一个原始值。
说完直接量,再来说变量。所谓变量,就是指向了某个直接量或者其他变量的“钥匙”。
比方说,把一个直接量 true 比作一扇门,然后定义一个变量 flag,最后通过赋值运算符“=”将这个 true 赋值给 flag,这样就完成了一个变量的绑定。
从此以后,你在别处使用变量 flag,也就相当于使用了直接量 true。简单来说,就是这么回事。
1.5 第五章 基础考核
叶小凡兴奋起来, 那种钻研了很久之后猛然地豁然开朗的感觉实在是太爽了。趁着心情大好,叶小凡继续钻研《Javascript 基础修炼要诀》,一晃半天时间过去了,叶小凡靠着自己谨慎的性格,刻苦的修炼,有很多问题即便弄懂了也想举一反三。
这段时间,如果肚子饿了,叶小凡就吃身上带的干粮,渴了就喝清晨的露水。这一幕幕都被那青山院掌尊看在眼里,他心里微微诧异,但更多的是欣慰。
“此子修行刻苦,虽然目前只是黄衣弟子,可这种修行的忍耐力和执着,哪怕是宗门护法,看到了也要心惊。”
在千鹤派,弟子根据修为的高低分为黄衣弟子和红衣弟子。红衣弟子之上,便是宗门护法,护法再往上就是掌尊。但凡成为红衣弟子,便会受到宗门的重视,修行资源和待遇也是水涨船高。如果有幸成为宗门护法,那更是一步登天,在每一个大院,宗门护法的权力仅次于掌尊!
而此刻的叶小凡,还只是最低一级的黄衣弟子。这期间,林元青时不时地会来到叶小凡的住处为他点拨一二。叶小凡在林元青的点拨下,很多之前想不通的难题都迎刃而解,他看向林元青的目光更是多了几分尊敬。
“叶小凡,下个月就是我们青山院基础考核的日子了!”林元青淡淡说道。
“基础考核?”叶小凡一愣。
“没错,在千鹤派,每个月都会有一次考核,如果考核进入前 5 名,除了能获得一笔宗门的奖励,还能收获不菲的贡献点。第一名甚至可以直接进阶为身份更高的弟子!”
叶小凡自知现在自己是黄衣弟子,一旦晋升成功,就能成为红衣弟子。临走前,林元青看了叶小凡一眼,说:“希望这次你别让我失望!”
说罢林元青弹指间绘出代码
var container = {gongfa:御剑飞行};
直接脚踏数据化成的剑乘风而起离去了。
这就是林掌尊吗?居然可以轻轻松松的御剑飞行,这得到了什么级别的强者,还有刚刚林掌尊绘出的是什么代码?
var container={};
似乎是林掌尊留给他的暗示,但叶小凡才刚刚弄明白数据类型,一时间叶小凡的压力增大了许多,毕竟从接触到现在也算是刚刚起步,也没有什么人指导。
一直到进入千鹤派才有了修炼秘籍《Javascript 基础修炼要诀》,现在距离下个月只有不到二十日的时间,想了想叶小凡接着翻开《Javascript 基础修炼要诀》开始修炼起来,并下定决心定不能让林掌尊失望。
次日叶小凡准备外出找寻灵感,但对青山院比较陌生,便想着就在周边看看,刚走出住宅看到师兄师姐们在外操练代码,其中一名师兄看到了刚出来的叶小凡便上直径走上前打招呼,
“你就是叶小凡吧,幸会啊,我叫王志宇”,“啊,你好你认识我吗?”叶小凡被这突如其来的热情所吓到了,
“我们整个青山院都知道你!”
“我才刚来青山院几日,你们怎么认识我的啊?”
“林掌尊说我们青山院新来了一名甲等资质的弟子,名为叶小凡,刚刚就看到你一个陌生的面孔,想必就是你了”此时的王志宇看到有个这么优秀的小师弟便显得异常的兴奋,周围的师兄弟姐妹听闻都朝着叶小凡看去。
“我…”还没等叶小凡开口就一群人挤了过来,“这就是叶小凡啊”,“哇!甲等资质”,“咱整个青山院也才就大师兄和大师姐是甲等”,一群弟子围着叶小凡议论个不停,'甲等资质原来这么稀有嘛…'叶小凡心想着。
很快议论随着掌门的到来随之而散,而叶小凡独自前往了青山院附近的一座小山上开始修炼了起来,每天清晨就去傍晚而归。
1.6 第六章 何老
这一日,叶小凡来到青山院附近的小山上修炼,忽然被一块石头绊 了一跤,正在他自认倒霉地打算爬起来的时候,他在石头缝里发现了一 枚古怪的戒指。戒指通体呈现枯黄色,似是年代久远。
“摔了一跤,捡到一枚戒指,算是补偿吧,虽然这枚戒指应该也没 什么用。”叶小凡随手拿起戒指,戴在了手上。就在这时,一股神秘的 力量从叶小凡的丹田之处涌了上来,这股力量将他置身于一个奇妙的空 间之中。
“哈哈哈,小娃娃,没想到我何老被封印了上千年,今天托你的福 终于重见天日啦!咦,你的修为怎么这么低,竟然连幼儿园的水准都没 有!罢了罢了,从今以后就由我来教导你,你最好给我尽快达到大学的 修为,这样我就可以真正地自由啦! ”
叶小凡被这突如其来的声音吓了一跳,惊慌地喊道:“怎么回事, 你是谁,你想干什么? ”
“我是何老,几千年前是这片脚本大陆的最强者,只不过不小心遭 人暗算,才被封印到了这枚戒指中。小娃娃,你现在的修为太低了,等 你到了大学境界,才能有力量把我放出来,我也就自由了。小娃娃,你 放心,等你把我放出来后,我绝对不会亏待你。跟着我,包你从今往后 吃香的,喝辣的。哎呀,小娃娃,你干什么?快住手,快住手! ”
叶小凡虽然谨慎、愿意吃苦,但到底是没有遇到过这么离奇的事 情。戒指里面封印着这片大陆的最强者,这听起来实在是有些天方夜谭 了。
叶小凡心想:“莫不是妖怪?嗯,对了,一定是妖怪!赶紧扔,赶 紧扔!”
“这不是真的,我一定是在做梦,妖怪爷爷,你可别来找我啦! ”说 着,叶小凡扬起他那只白嫩的小手,手心里攥着那枚刚捡来的戒指,就 要扔到悬崖下边。
“小娃娃,你快住手啊,我说的是真的,我可不是什么妖怪,我是 何老啊。哎呀呀,我好不容易等来一个人,这么多年都等下来了,你这 要是一扔,我又要等到猴年马月才能有希望出来了! ”何老这下真的急 了,语气丝毫没有了之前的从容和兴奋,有的只是惊慌和无奈。
“哼,你还嘴硬,还说你不是妖怪,你当我傻啊。大学境界是传说 中才有的境界,根本没有人可以修炼到。我们宗门的太上大长老,也不 过是高中境界,可即便是这样,他也有只手遮天的能力了,看我不扔了 你。”说着,叶小凡又要扔。
“哎呀呀,好了好了,我不说了好不好?对了,小娃娃,你来问我 问题啊,在这片大陆上,JavaScript 的功法和心得还没有什么可以问倒 我。”何老都要哭了,真没想到自己好不容易有了重获自由的希望,却 马上就要泡汤,能不能不这么刺激呀?
叶小凡听到这话,愣了一下,心想难道这是真的?可转念一想,天 知道这个老妖怪在打什么主意,还是扔了好,扬起手又要扔。
“小娃娃,你可知道对象数据类型? ”何老吼道。
1.7 第七章 对象数据类型
听到这句话,叶小凡一顿,动作停止了下来。
对象数据类型在《JavaScript 基础修炼要诀》中只是提了一下,并没 有细讲,它深深地勾起了叶小凡的好奇心。
“小娃娃,相见是缘,既然你感兴趣,我便教你一教。你且听好, 在 JavaScript 中,数据可分为两类,分别为原生数据类型和对象数据类 型。所谓对象数据类型,是一种复合型的数据类型,它可以把多个数据 放到一起,就好像一个篮子,这个篮子里面的每一个数据都可以看作是 一个单元,它们都有自己的名字和值。”
叶小凡被何老的话深深地打动了,立刻聚精会神地听起来。
“现在你相信了,我可还是妖怪? ”
叶小凡嘿嘿一笑,重新把戒指戴好。
“小娃娃,你叫什么? ”
“叶小凡! ”
“嗯,老夫姓何,你姑且叫我何老吧。小娃娃,我且问你,你可愿意拜我为师? ”
“弟子愿意!”
说完,叶小凡将戒指摘下,放在身前的岩石之上,开始行拜师之 礼。何老虽然没有形体,但是却能看得一清二楚,待所有礼节完毕,叶 老这才满意地点了点头。
“很好,小娃娃,你可听好。对象数据类型比原生数据类型强大了 不少,原生数据类型,比如数值型、浮点型、布尔型等都只能存放一些 直接量,也就是说单一的数据。而对象数据类型却是可以存放一大堆数 据的集合,这些数据都有自己的名字,比如…”
忽然,叶小凡心头一亮,感觉有清晰的画面传来。
“我现在没有形体,但是我却可以用意念来给你做演示,小娃娃, 你且看好。现在我给你创建一个对象。”
var container = {};
“创建对象就是用一个大括号吗? ”
“这是创建对象的一种方式,也是最常用的方式。创建对象以后, 就相当于开辟了一块内存,对象包含若干数据,每个数据都有自己的名 字和值。对象好比是一个容器,现在我要在这个容器里面放一个数据, 你且看好!”
var container = {
caoyao: "草药",
};
“小娃娃,你可看明白了? ”
“前辈,您刚才说对象数据类型里面可以放若干数据,那现在它里 面是不是已经有了一个数据,数据的名字叫作 caoyao,它的值是字符串 类型的草药? ”叶小凡两眼放光,回答道。
"嗯,小娃娃,看来你的悟性还可以。在这个例子中,caoyao 叫作键,草药叫作值,它是一种键值对的形式。”何老哈哈大笑。
“键值对,键值对,一个键对应一个值,一个键和一个值就凑成了 一对,键和值中间用冒号。哦,我明白了! ”叶小凡恍然大悟,随即又 问道:“那么,前辈老爷爷,您刚才不是说对象数据类型里面可以放若 干个数据吗?现在里面只有一个,怎么添加第二个呢? ”
“嗯,小娃娃,这个问题问得很好。你且听好,如果你想要在一个 对象里面添加新的数据,则只需要添加一个逗号,然后写上新的键值 对就行了。”
var container = {
caoyao: "解毒草",
feijian: "乌木剑",
};
“小娃娃,我现在给你演示的方式是在创建对象的时候立刻在对象 里面设置键值对。其实还有其他办法,那就是在对象创建之后,在外面对这个对象的变量进行操作。你且看好,我现在用新的办法改写刚才的 例子。”
var container = {};
container.caoyao = "解毒草";
container.feijian = "乌木剑";
“虽然我不太明白这里面的玄妙,但是我大概猜到了 caoyao 是 container 这个对象的属性,似乎就是把刚才写在“{}”里面的东西又在外 面重新写了一次的意思吧。”
"嗯,孺子可教,container.caoyao 中的点(.)就是对象访问属性的 意思,正因为 caoyao 是 container 的属性,所以 container 才可以用点
(.)O 对象包含若干数据,每个数据都是一个键值对,这些数据也叫 作对象的属性。那么键值对中的键就是属性名称,键值对中的值就是属 性值。”
“我明白了,但是我还有一个疑问,如果对象用点(•)访问一个根 本不存在的属性会怎样呢? ”叶小凡问道。
“好问题,就比方说刚才的例子,如果我直接访问一个根本不存在 的属性 danyao,那么会怎样呢?小娃娃,看好!”
var container = {};
container.caoyao = "解毒草",
container.feijian = "乌木剑"
console.log(container.danyao);
// 注意:丹药这个属性是不存在的结果是undefined。
"我明白了,danyao。这个属性不存在于 container 对象中,因此它是 未定义的,得到的结果就是 undefined! ”叶小凡惊呼。
1.8 第八章 对象的取值
“小娃娃,我现在问你,如果我不知道对象的某个属性叫什么名字,那么又该怎么访问对象中对应这个属性的值呢?”何老笑呵呵地问道。
“什么什么,事先都不知道对象的属性名称,怎么可能访问得到啊?
这我可不知道,我想这是不可能的。”叶小凡想了想,赶紧摇头。
“这样吧,我换一种说法。我想你现在已经知道对象可以通过一个点号(.)访问其中的某一个数据了。”
说着,何老随手一挥,一个对象就生成出来了。
var container = {
caoyao:"解毒草",
feijian:"乌木剑"
};
“我现在想用到解毒草,就直接用 container 调用它的 caoyao 属性。”
container.caoyao
“这样做的确是可以的,但是如果遇到这种情况,即事先不知道调用的属性叫什么名字,那么该如何用一个变量定义属性呢?”
说着,何老又随手一挥,定义了一个变量。
var container = {
caoyao:"解毒草",
feijian:"乌木剑"
};
var prop = "caoyao";
“这……”叶小凡也陷入沉思,过了许久,缓缓说道:“直接点 prop 肯定不行,那样的话,container 调用的肯定是一个叫作 prop 的属性。而事实上,container 对象里面是没有叫作 prop 的属性的,得到的结果肯定是 undefined。”
听到这里,何老向叶小凡投去了赞赏的目光,继而说道:“你的分析没有错,这里不能再用之前的那种方法了。小娃娃,你且看好!”话音刚落,何老就打出了新的代码。
var container = {
caoyao:"解毒草",
feijian:"乌木剑"
};
var prop = "caoyao";
console.log(container[prop]);
“这!”叶小凡惊呼。
看着叶小凡惊讶的样子,何老似乎有些得意。
“小娃娃,这就是我教你的新技巧,对象不仅可以用点号(.)访问它的一个属性,也可以用中括号([])。如果用中括号,里面就允许再写一个变量。当然了,写字符串也是可以的。”
似乎是担心叶小凡理解不了,叶老又补充了一行代码。
console.log(container[prop]);
过了好一会儿,叶小凡才回味过来,说道:“我明白了,如果事先属性的名称未知,或者调用的属性是动态变化的,就不能使用点号了。使用中括号可以最大程度地提升对象调用属性的灵活度!”
1.9 第九章 循环遍历的奥妙
“小娃娃,我且问你,可否知道循环遍历的法术? ”
“循环遍历不就是 for 循环或者 while 循环吗,这有何难? ”
说着,叶小凡就随便打出了一段代码。
for(var i= 0;i < 10;i++) {
console.log(i);
}
“嗯,你使用的是 for 循环。如果你希望一遍又一遍地运行相同的代 码,并且每次的值都不同,那么使用循环是很方便的。
就好像你刚才 写的,你想要重复使用 console.log 输出一个东西,使用 for 循环的确可行。那你可知 while 循环? ”
叶小凡想了一下,说道:"感觉 while 循环和 for 循环差不多吧,就是 它们在语法上稍微有点区别。”
说着,叶小凡随手打出一段代码,将刚才的 for 循环改写成了 while 循环。
var i = 0;
while(i < 10){
console.log(i);
i++;
}
“i++是自增运算符,表示把当前的变量自增一个单位。而 ++i 和 i++是有区别的,前者代表先自增一个单位,再运算;
后者相反,表示 先运算,再自增一个单位。
但是由于这段代码中的 i++占单独一行,没 有对 i 进行使用,所以不管是 ++i 还是 i++,只要这句话执行完毕,i 的值 都会自增。”
听到这里,何老满意地点了点头。
"小娃娃,看来你的基础不错,那你说说 while 循环和 for 循环除了语 法还有什么区别。
“这 …”叶小凡一时语塞。
“小娃娃,你且看好,你方才写的 for 循环中有一个小括号。
小括号 里面有 3 个表达式,分别为“var i=0”,“i < 10”还有“i++”。第 1 个语句是在 循环开始之前执行的,“var i=0”的意思是定义了一个变量 i,是整数,初 始值为 0。第 2 个语句是“i < 10”,表示进入循环体的条件。”
“循环体就是那个用大括号({})扩起来的部分吗? ”叶小凡问道。
for(var i=0;i < 10;i++) {
console.log(i);
}
“没错,不论是 for 循环还是 while 循环,循环体就是这个部分,这个 部分里面的代码是需要被多次执行的。
现在我再给你说说最后一个语 句“i++”,这个语句是在刚才我们所说的大括号里面的代码被全部执行 之后才会被执行的。
一般来说,上面这段语句里面的代码可以控制循环 变量 i 自增一个单位或者自减一个单位。”
“自增我知道,无非就是 i++或者 ++i,为什么要自减呢? ”
“关于这个问题,是和第 2 个语句相关联的。比如你刚才写的代码。”说着,何老指向叶小凡刚才写的代码。
for (var i=0; i < 10; i++) {
console.log(i);
}
“你的循环判断条件是当 i < 10 的时候才会进入循环体,也就是后面 用大括号扩起来的部分,对吧? ”何老问道。
“没错,最开始的时候 i=0,第一次循环中 i 自然是小于 10 的,于是就 进入了循环体,像这样。”说着,叶小凡催动内力,让这段 JavaScript 代 码开始执行。
当执行到这一行代码的时候,叶小凡特意让代码停了下来,调试代码。
“嗯,很好,我且问你,现在代码停在了这一行,如果我再往下执 行一步,那么会到第 4 行还是停留在第 3 行呢? ”何老问道。
“那还用问,肯定是跳到第 4 行啦。”叶小凡十分肯定地说道。
“先别着急下结论,走一步试试。”何老对叶小凡说道。
“试就试。”说着,叶小凡就用 debug 走了一步。
“这是怎么回事?”叶小凡讶然,同时皱了皱眉。
“你再走一步试试。”何老笑呵呵地说道。
就这样,叶小凡又走了一步,这才发现走到了第 4 行。经过反复测试,叶小凡紧皱的眉头终于松开了。
原来,第一次跳到第 3 行代码的时候,是在准备运行 for 循环的语句 1,也就是“var i=0”这句话。
因此,刚才第一次跳到第 3 行代码的时候,i 变量的值是 undefined(未定义),因为这个时候只声明了 i 变量,还没有运行“i=0”这个赋值语句,所以是 undefined。
而当叶小凡往下再走一步的时候,则是运行了“i=0”这个赋值语句,这个时候,i 变量的值才如愿以偿地变成了 0,整个语句 1 才算是执行完毕了。
为什么再走一步就能够跳转到第 4 行代码呢?这是因为语句 1 执行完毕后就自然会执行语句 2 了,也就是“i < 10”这句话,这就好比是一个 if 判断。
var i = 0;
if(i<10){
console.log(i);
}
第一次循环的时候,i=0 自然是小于 10 的,因此直接进入了循环体。循环体执行完毕后,开始执行语句 3——“i++”,i 从 0 变成了 1,然后进入第二次循环,再次判断 i 是否小于 10。
听着叶小凡的论述,何老微微点了点头,说道:“是这样的,那么问题来了,在刚才的例子中,i 从 0 一直自增到 10,当然,它最后会变成 10,但是却无法再次满足 i < 10 的判断条件了。所以,当 i=10 的时候,就无法进入循环体了。
可是这并没有关系,因为第一次 i=0 是符合条件的,最后一次进入循环体是在 i=9 的时候,像这样。”说完,何老随手一挥,将这段代码的运行结果显示了出来。
0
1
2
3
4
5
6
7
8
9
“嗯嗯,我明白了,因为 i 变量是从 0 开始的,所以 0~9 还是循环了 10 次。
至于刚才说的自减,其实也是一样的,只要改变一下循环条件和初始化变量 i 的值就行了。”
说完,叶小凡修改了一下代码。
for( var i = 10;i > 0;i--){
console.log(i);
}
“同样是循环 10 次,这回就是变量 i 从 10 减到 0 的过程了。”叶小凡说道。
“没错,是这样的。while 循环只是在语法上有所不同,其作用和 for 循环是一样的。很好,看来你已经掌握了循环的奥妙。”
小结:
For 循环
for 循环是您在希望创建循环时常会用到的工具。就是说,如果某一段代码你需要多次执行,如果不用循环,则需要将相同的代码重复多遍。
下面是 for 循环的语法:
for (语句 1; 语句 2; 语句 3)
{
被执行的代码块
}
语句 1 在循环(代码块)开始前执行
语句 2 定义运行循环(代码块)的条件
语句 3 在循环(代码块)已被执行之后执行
while 循环
While 循环会在指定条件为真时循环执行代码块。
语法
while (条件)
{
需要执行的代码
}
1.10 第十章 对象内容的遍历
“既然你现在已经知道了如何使用 for 循环,那么现在我就来教你如何用这个技术遍历一个对象。”何老说道。
“对象里面无非就是属性和函数,你的意思是给我一个对象,想办法获取它里面所有的数据(键值对)吗?”
“没错,假设有这样的一个场景:我需要判断一个对象中哪些东西是属性,哪些东西是函数。这就需要我依次获取这个对象里面的所有东西,然后判断谁是属性、谁是函数。”
“等等,就算拿到了这些东西,怎么才能判断谁是属性、谁是函数啊?我好像还没有这方面的法术。”
“不用担心,这个很简单,你只需要用一个 typeof 关键字就可以了。
比如,我现在有一个字符串和一个函数。”说着,何老写出了如下代码。
var a = "123";
var fun = function () {};
“然后,用 typeof 关键字包裹一下,再输出看看。”
console.log(typeof a);
console.log(typeof fun);
“看到了吧,这样就可以得到变量的类型了。a 是一个字符串,所以 typeof 出来就是 string;
fun 是一个函数,所以 typeof 出来就是 function。接下来,我来跟你说说如何遍历一个对象。
首先,新建一个简单的 JavaScript 对象。
var yeXiaoFan = {
name : "叶小凡",
age:16,
eat:function(){
console.log("KFC");
}
}
“然后使用 for 循环进行遍历。”
for (var p in yeXiaoFan) {
console.log(p);
}
“这个 for 循环和之前的写法是不同的。其中,p 是一个随便取的名称,代表 yeXiaoFan 对象中遍历出来的属性名称。通过这种方法,我可以在事先不清楚对象有哪些属性的情况下把属性的名称都获取到。”何老缓缓地说道。
效果:
“那么除了属性名称,属性的值也可以得到吗?”叶小凡眨了两下眼睛,好奇地问何老。
“属性名称都得到了,你还愁没有属性值吗?”何老一吹胡子,笑呵呵地反问。
“啊,我明白了,既然有了属性名称,那么对象可以用点(.)的方式直接获取属性的值。当然,用中括号([])也是可以的。”
叶小凡恍然大悟。看到他如此表现,何老也不禁点了点头。
“没错,是这样的。我们只需要把刚才的代码稍做修改就可以了。”说着,何老又打出一段代码。
var yeXiaoFan = {
name:"叶小凡",
age:16,
eat : function(){
console.log("KFC");
}
}
for(var p in yeXiaoFan){
console.log(p + "=" + yeXiaoFan[p]);
}
“成了,可是你刚才为什么不用点号?”叶小凡嘀咕道,可是转念一想就明白了其中的缘由。
因为遍历出来的属性名称是不确定的,而是用一个 p 变量指代,既然是变量,自然不可以用点号。
因为如果写成 yeXiaoFan.p,那么就会被认为是寻找一个名字叫作 p 的属性,然而事实上,p 只不过是一个变量的名称而已。
换句话说,p 随便叫什么都没关系,反正它只是一个变量的名称罢了,真正重要的不是 p 变量叫什么,而是 p 变量指代的内容是什么。
“我看到你的表情就明白你已经懂了,没错,你的猜想是正确的。一旦遇到这种属性名称不确定的情况,就只能用一个变量代替,换句话说,不能用点号,只能用中括号。因此,当对象访问属性的时候,用中括号是更加灵活的。”
“那么,我是不是应该时刻都用中括号,再也不用点号了?”
“那倒不一定,有些情况,或者说绝大多数情况还是用点号。因为大部分的情况下,你都是已经明确知道属性的名字叫什么了,那么毫无疑问,用点号是更加方便的,你说是吧。”
叶小凡想了一会儿,然后点了点头。
1.11 第十一章 外门小比
一转眼,大半个月已经过去,这段时间,叶小凡一有时间就会向叶老讨教相关的 JavaScript 知识。以叶老的广博见闻和技术底蕴,自然没有问题。叶小凡也因此受益,这段时间功力突飞猛进。
“马上就要到一年一度的外门小比了,听说这次我们需要和紫云派的弟子进行外门小比。去年我们派险胜了对方,对方一直不服气,据说这次是有备而来,而且出了一个天才的新人少年,叫什么简南的。”
“紫云派超级新人简南,不会是那个变态吧。年纪轻轻已经被内定为下一代掌门的候选人,据说紫云派这么多年来就出了这一个资质绝佳的天才。”
叶小凡走在路上,时常听到人们在议论这些事情。
“外门小比,似乎有点意思,要是我能够入选去参加比赛就好了,那样也可以给门派争光!”叶小凡想到。没想到,下午青山院就接收到了通知,要求从新入门的弟子中挑选一位最具天资的人,去参加即将开始的外门小比。一石激起千层浪,新入门的弟子可不止叶小凡一个人,一个个摩拳擦掌,跃跃欲试。
为了挑出最为合适的人选,林元青特意在演武场召集了青山院所有的记名弟子,包括叶小凡在内,共计 16 人。要想获取参赛的名额,就必须得在这 16 人中脱颖而出。
演武场,占地约 2 亩,是每个院定期切磋技艺的地方,16 名弟子被安排在一块用大理石砌成的场地上,两两相对,即将进行参赛名额的角逐。
“我是青山院的林元青,今日,我需要在你们中间挑选出一个人选,去参加即将到来的外门小比。比赛采用两两对决的形式,胜利的一方进入下一场比赛,失败者直接退出。”
这种比赛对于一些来看热闹的大弟子来说并不陌生,反正每年都是这样搞的。前来观赛的还有很多其他院的弟子,他们无非也是想来看看这一届的新人有没有什么特殊。
“好吧,比赛开始,第一场的题目:运算符,每个弟子根据自己的理解,详细概述一下运算符,然后由我来评判谁可以晋级。”林元青淡淡的说道,随后就身形一飘,继而稳稳地出现在了裁判席。裁判自然不止林元青一个人,还有其他几个院的掌尊,今天也一并来了。
“什么,运算符?”有些基础不好的弟子听到这个词,当即皱起了眉头。
1.12 第十二章 JavaScript 运算符
题目一出,真是几家欢喜几家愁,那些复习得好的,自然可以款款而谈,可是那些本身基础就薄弱的弟子,立刻尴尬地说不出话来。甚至有的弟子已经举双手表示要放弃比赛。这些举动立刻引来了场外那些大弟子的哄笑。尤其是其他院的弟子,更是偷来睥睨的目光。
林元青见此,暗叹一声,却是没有特别失落。根据往年的经验,青山院的弟子生源都是最差的,学得好的人更是凤毛麟角。在这一点上,青山院的弟子行走在宗门,也总是要比别人地上一头。
“哈哈,不愧是吊车尾的青山院,今年果然还是老样子,运算符这么简单的东西,都能难住这么多人。”
“就是啊,不过是运算符罢了,我看啊,青山院迟早退出内门,成为外门院系吧。”
第一场,是叶小凡和一个同门弟子之间的对决。
“快看,那个叫做叶小凡的,据说是才入门不久,依我看呐,运算符虽然简单可也不是这样一个刚入门的愣头青能够理解的。”
“就是啊,这不,他现在的脸色可真是要多难看有多难看啊,哈哈。”
没有理会这些流言蜚语,叶小凡眉头紧锁。这倒不是说运算符这么简单的基础他不会,而是因为叶老这个 BUG 级的老家伙存在,平时没少给叶小凡灌输一些比较深奥的功法和概念。因此,哪怕只是简简单单的运算符,叶小凡也在好好思忖该如何去概述。就在这时,对面传来了一阵傲慢的声音。
“嗨,你就是那个什么叶小凡吧,算你运气不好,碰上本大爷我。我可告诉你,JavaScript 基础功法中,可没有什么功法可以难得住我的。所以,我劝你啊,早点认输投降吧。我看你对运算符也不是特别熟悉,何必在这丢人现眼呢,哈哈哈!”
叶小凡抬头一看,只见一个和自己年龄相仿的少年,已经开始自信地解释起来。
“JavaScript 运算符,无非加减乘除和赋值运算,何难只有?赋值运算符用于给 JavaScript 变量赋值。比如我现在有一个变量,var a; 那么这个变量的值就是 undefined,因为没有定义嘛。然后,我当然需要给它赋值咯。赋值的方法,就是用=号,把真正的值用=号赋给它,这个就叫做赋值。加减乘除,自然不用多说,不就是最简单算数嘛。比如说,我有两个变量,先用赋值运算符给它们赋值,然后计算加减乘除。这样吧,我写一段代码就全清楚了。”
var a = 10;
var b = 2;
var s1 = a + b;
var s2 = a - b;
var s3 = a * b;
var s4 = a / b;
“叶小凡,这段代码你能看得懂吗,我想你也是看不懂的吧。好了,本大爷我就大发慈悲,跟你解释下好了。”
林元青注视着这一切,没有多说话,根据演武场的规则,比赛双方切磋技艺,可以互相跟对象提问题,然后根据双方的作答情况和问题的质量,由评审团打出一定的分数。
“老林啊,看来这个弟子基础还可以啊,而且还很自信啊。想必这应该就是你们院的最高水准了吧”说话的是绿水院的掌尊-尹曾琪,平时和林元青关系不太好,常常喜欢冷嘲热讽。林元青笑了笑,并不答话。
“叶小凡,你可听好了,我就说一遍而已。因为 a=10,b=2,所以两者加起来就是 12。所以,s1 等于 12 错不了。a-b=8, a*b=20, 最后是除法,10 除以 2 自然是等于 5 啦。”
“好,现在轮到我说了。你方才讲得是自然不错,但是除了加减乘除,还有三个运算符没有说到。”叶小凡淡淡地说到。
“什么,那你说说还有什么运算符?”对面弟子不可置否地说到。
“首先是取余数的运算符 — ‘%’,取余数的意思就是一个数字除以另一个数字,除不尽的部分就是余数。比如 5 除以 2,得到的结果就是 2.5。当然,0.5 是小数了,既然要取余数,自然就不能写成小数形式。5 除以 2,能够被整除的就是 2(5=2x2+1),余下来 1。因此,余数就是 1。还有一种情况,小的数字除以大的数字,一个都不能被整除,比如 2 除以 5,那么余数就是 2 本身。”
“啊,啊,是的,我正想要说呢,还有取余运算符。”
“除了取余运算符,还有自增运算符还有自减运算符。自增运算符是++,自减运算符是–。顾名思义,自增和自减运算符可以使得当前的变量自增一个单位或者自减一个单位。这里有一个需要注意的点,不管是自增运算符还是自建运算符,都分为两种。比如有这样一个例子。”
var a = 10;
var b = a++;
console.log(b);
这样写,得到的结果你猜等于几。
“哼,这有何难,自增代表的是自增一个单位。既然写了 a++。那么 b 自然就是 11 了,你当我傻啊,问我这么简单的问题。”对面弟子气呼呼地说到。
“错了,答案是 10。”叶小凡淡淡地说到,并且运功执行了这一段代码,只见得到的结果为:10。
“这,这,怎么可能?”对面顿时惊呼道。
“对于自增运算符来说,分为前置++和后置++。前置++,故名思议,就是在变量的前面写一个++。后置++就是在变量的后面写一个++,在我刚才的例子中,就是后置++。后置++的特点就是先让变量去参与运算,运算结束以后再进行++。好,再看一下我刚才举的例子。”
var a = 10;
var b = a++;
console.log(b);
“尤其注意第二句。”叶小凡用手指着第二句 var b = a++说道,“因为 a++里面的++是放在后面的,那么这个就是后置++。后置++的意思就是先把 a 原来的值放进式子里面去运算,然后在++。也就是说,在这个赋值语句中,赋给变量 b 的值依然是 10,而不是++后的 11。”
“切,这有什么难的,我刚才不过就是一时疏忽罢了。如果我现在把题目改一改,就是 11 了吧。”对面弟子听完后感到破不服气,于是大手一挥,把代码改了改。
var a = 10;
var b = ++a;
console.log(b);
写完后,只见他想了想,终于鼓足勇气说道:“这个就是前置++了,++a 的意思是先让 a 的值去++一次,a 本来是 10,经过++就变成了 11。因为前置++的含义是先把变量放进式子里面去运算,然后++,所以这个代码的结果就是 11。”
说完,对面弟子开始运功,将代码执行了一遍。果不其然,得到的结果是 11。
“自然是这样的,但是我现在这样改一下,你说结果是多少呢?”叶小凡诡异地笑了笑,然后打出一段比较奇特的代码流:
var a = 1;
var b;
var sum = (b = a++ + --a) + a-- + b++;
“噗!”看到这段代码,对面弟子差点一口老血喷出来,破口大骂:“叶小凡,你…你…你欺人太甚!”
“呵呵,这位师兄,这可怨不了我,再说了,互相切磋技艺也是演武场的规矩啊。”叶小凡回想起这段被叶老折磨的日子,再看到面前这位弟子的表情,顿时开心了不少。其实这道题目就是叶老给叶小凡在平时练习的时候出的。
“这个小娃娃倒是有趣。”绿水院掌尊尹曾琪看到叶小凡竟然能出这样的题,纵使是他也眼前一亮,不由称赞到。当然,以他目前的修为,这种题目自然是难不住他的。但是,很难想象,一个刚刚进入山门的小娃娃,竟然有如此造诣,实在是让人惊讶。
“这道题看似复杂,其实只要一步一步拆分,也是可以分析理解得很透彻的。”林元青微微一笑,看着叶小凡的目光中多了几分期待。毕竟,叶小凡是他亲自带上山来的,多少有一点印象。而且,他也很想看看,叶小凡如何来解答这道题。
“天哪,这种题目也太变态了吧。这个叶小凡真是不知道天有多高,地有多厚。”
“就是,就是,依我看呐,这根本就是那个叶小凡随便瞎掰的一道题,我看他自己多半也做不出来。”
“哈哈,师兄说的是啊,就让我们一起看看他如何出糗吧”
场外有很多资历比叶小凡高出不少的大弟子,有好多刚才试着算了一下,但是马上就自行放弃了,纷纷向叶小凡投来鄙夷的目光。但是,其中有一道目光却充满了严肃,这个人是一个和叶小凡年龄相仿的少年,旁边有几个功力深厚的年轻人跟随。这时候,一个长相略显老气的人对该少年说到:“少爷,您是当今台上大长老最喜爱的长孙,资质和天赋也是我派数百年来的翘楚。依属下看,这个叶小凡真是不知天高地厚,多半是随便糊弄了一下,自己都不知道这道题怎么解。”
只见少年把手一样,那名男子立刻不说话了。接着,他饶有兴趣地看着叶小凡,这人便是门派内定地继承人,门派中台上大长老最喜欢的长孙:罗丹。
1.13 第十三章 语惊四座
“叶小凡,现在你把这道题解释一下吧。”林元青看向叶小凡,稳重但不缺威严地说到。
“是。”弟子面朝着林元青作揖,然后抬起兄,缓缓到来。
“这道题看似复杂,但只要一步一步细细分开来,还是有迹可循地。”
var a = 1;
var b;
var sum = (b = a++ + --a) + a-- + b++;
“首先,变量 b 只是定义了一下,没有赋值,在运行第三句话之前 b 的值就是 undefined,表示未定义。”
“嗯,继续说。”
“弟子遵命,接下来就是第三行代码。这一行代码比较长,我试着把它拆分出来。首先是这一句。”
b = a++ + --a;
“很明显,这是一个赋值语句,a++是后置++,那么就先把 a 的值放进去运算。这个时候,a 的值还是 1。但是,一旦 a++结束,a 的值就变化为 2 了。换句话说,当执行到后面的—a 的时候,a 的值就是 2。”
“重点来了,又因为—a 是前置–,意思就是先 – 然后放进式子中去运算。刚才我们说到 a 的值已经是 2 了,那么—a 在这里就变回了 1。因此,这句话 b 最终的值就是 1 + 1 = 2。”
s
“我这边用了括号,括号是为了让变量 b 的赋值语句先进行运算。所以,b 的值在后面参与运算的时候就已经是 2 了。刚才 a 最后的值是 1,那么后面的 a—因为是后置–,参与运算的值还是 1。因此,(b = a++ + --a) + a—的结果就是 3,a—过后变成了 0,不过后面和 a 没有啥关系了,那么就不管 a。最后一个是 b++,由于是后置++,所以 b 变量参与运算的值还是 2。那么,最终的答案当然就是 3+2=5 啦。”叶小凡轻松地说着,似乎根本不觉得这是什么困难的事情。
寂静,一片寂静。
就连对面的弟子也听得津津有味,场外的一众大弟子们个个屏住了呼吸,看着叶小凡。似乎,自己还真的小瞧了这个刚进山门的小师弟。罗丹双眼死死盯住叶小凡,脸色从未有过的凝重。
“嗯,不错,你理解的很好。”林元青不吝称赞,一种欣慰的感觉油然而生。
第一场,自然是叶小凡获胜。
1.14 第十四章 秀
第一场,叶小凡赢得非常漂亮。这难免遭到了场外一些大弟子的嫉妒。
“哼,不过才赢了第一场,有什么好神气的。”
“就是,就是啊。不管怎么说,运算符毕竟还是属于比较基础的功夫。我看哪,这个叶小凡只是运气好,正好对这一块比较熟悉罢了。”
第一场结束,16 进 8,叶小凡成功晋级。第二场的题目,是一道计算题。用 JavaScript 计算 1+2+…+100 的值。就在对面还在埋头苦算的时候,叶小凡已经完成了代码。
var sum = 0;
for (var i = 1; i < 101; i++) {
sum = sum + i;
}
console.log(sum);
在对手震惊的目光中,叶小凡不慌不忙地开始解释。
“计算 1+2+…+100 的值,直接一个一个加肯定不行,太慢了,效率太低。因此我想到了用循环。for 循环是一个不错的选择。for 循环的格式,圆括号里面有三个表达式,当需要进行 for 循环的时候,就先执行表达式一。也就是 var I = 1。然后执行表达式 2,I < 101。表达式 2 是一个判断条件,和 if 语句判断有异曲同工之处。当表达式 2 的结果为布尔类型的 true 时,就认为符合进入循环的条件,于是接下来就回去执行{}中的内容。”
sum = sum + i;
“在{}里面,就是一个累加的操作,把每一次循环的 i 加到变量 sum 上去。当执行完这些代码后,才会去执行表达式 3,也就是 i++,这句话的意义是让 i 变量自增一个单位,好让 i 一直在慢慢变大,直到不符合进入循环的条件为止。”
听到这里,林元青微微点了点头。叶小凡继续说道,“我想这道题的考核要点就是对循环技术的了解程度。其实,这样一道题目也可以用 while 循环来做。”说着,叶小凡又重新打了一段代码:
var sum = 0;
var i = 0;
while (i < 101) {
sum += i;
i++;
}
console.log(sum);
“while 循环和 for 循环的不同之处,在于 while 循环只有一个判断的表达式,就好比刚才 for 循环中的表达式 2。至于 for 循环的表达式 3,已经放到{}中去了。表达式一则放到 while 循环之前去了。就有点类似于这样。”说完,叶小凡又写到,过程没有滞缓,完全行云流水,好像已经烂熟于心似的。
var sum = 0;
var i = 0;
for (; i < 101; ) {
sum += i;
i++;
}
console.log(sum);
“嗯,做的不错,这一场,自然还是叶小凡胜了。”林元青宣布比赛结果。
“啥?又赢了,这也太轻松了吧。”叶小凡心中暗惊,也难怪,叶小凡平时都是在叶老的指点下,这种难度的题目实在是有点小儿科了。
叶小凡二连胜,八进四!
1.15 第十五章 天秀
下一题的内容是 JavaScript 数组,比赛双方需要说出自己对于数组的理解,最后由掌尊林元青来判断谁可以胜出。听到这个题目,叶小凡差点笑出声了,关于数组,自己都不知道和叶老那个老怪“交流”多少回了。虽然不知道自己对于数组的掌握有多深,但是叶小凡依然有着十足的信心。于是,就在对面还在冥思苦想的时候,叶小凡已经滔滔不绝地讲述了起来。
“在 JavaScript 中,数组是一个非常灵活的类型。简单来说,数组就是一个容器,可以存放一个或者多个对象。当然了,这些对象的类型是没有限制的,不管它是什么,数组都可以存放。”叶小凡非常淡定地说道,中间没有丝毫停顿,好像这些话早已经融入自己的灵魂深处一样。
“喝!说得好像你很懂似的,姓叶的,你倒是先说说,数组该怎么创建吧!”对面弟子不屑一顾地说道,对于数组,他虽然不敢说非常精通,但是多少有点了解。更关键的是,自己可是比叶小凡早入门许久,当然不认为自己会比叶小凡这个新人差。他已经准备好了,计划随时打断叶小凡的讲述。
“数组有四种定义方式。”叶小凡随机讲到。
“什么,四种?笑死人了,我倒还是第一次听说,数组不就是一对中括号就定义了吗,哪来的四种?”对面弟子不屑一顾地笑了笑。
“你说的是用直接量定义数组。”叶小凡继续说道,“所谓直接量定义,就是用一对中括号来声明一个数组对象,就像这样:”
var arr = ["first", "second", "third"];
console.log(arr);
“得到的结果,就是生成了一个拥有三个元素的数组对象,对象的名字是 arr。这种方法的好处是在定义数组的时候,就直接给这个数组做了初始化。除了这种方法,还有其他三种方法,我先来说第二种。”说着,叶小凡就打出一段代码流:
var a = new Array();
“这是采用构造函数的方式创建一个数组对象,在 JavaScript 中,每一个类型其实都有一个函数作为支撑,数组也不例外。在这个例子中,Array 也叫作构造函数。和这第二种方法类似的,还有两种方法,也是采用构造函数的方法来创建一个数组对象的。”
var b = new Array(8);
var c = new Array("first", "second", "third");
“这三种方式有着各自的区别,第一种是直接用构造函数创建一个空的数组,也就是说,这个数组里面什么都没有。数组天生就拥有一个 length 属性,我可以让这个 a 变量调用自身的 length 属性来验证这一点。”
var a = new Array();
console.log(a.length);
代码运行,众人看得清清楚楚,结果是一个“0”。
“相信各位也看到了,这段代码的结果是一个 0,这表示当前的数组对象里面啥也没有。接下来看第二种方式。”
var b = new Array(8);
“这种方式和刚才那种有所不同,不同点就在于,它虽然也是创建一个数组,但是却在创建的同时,设置了一个初始的长度,大家看,Array 是一个函数,new 关键字表示在创建这个函数所表示的对象,因为是函数,所以自然是可以打括号的。没错,函数可以打括号,打括号的意思就是要去执行这个函数的函数体。函数是有参数的,这个 8 就是参数。在这个例子中,8 就表示给数组对象添加一个初始化的长度,我依然可以用数组的 length 属性来验证这一点。”说着,叶小凡继续打出代码:
var b = new Array(8);
console.log(b.length);
代码运行,众人看得清清楚楚,结果是一个“8”。
“相信各位也看到了,这段代码的结果是一个 8,这表示当前的数组对象里面已经有 8 个元素了。那么问题来了,我并没有给这个数组添加任何东西,最起码,看起来没有。那么,这八个元素到底是什么呢?这个待会说,先看最后一种方式。”
var c = new Array("first", "second", "third");
“这一种方式在创建数组对象的同时,就给它赋予了初值。简单来说,就是创建数组的时候,就给它添加了三个元素。正因为如此,所以这个数组当前的 length 属性已经有值了,而且就是里面元素的个数:3!”
var c = new Array("first", "second", "third");
console.log(c.length);
代码运行,众人看得清清楚楚,结果是一个“3”。
“什么,叶小凡,你…竟然连函数都知道了!”对面弟子瞪大了眼睛,一脸的难以置信,要知道,在 JavaScript 初级阶段,函数可是一门了不起的法术了!虽然也有一些悟性好的弟子提前对函数有了一知半解,但是大部分初级弟子都是无法驾驭函数的。
“还是继续讲讲数组吧,刚才我一共说了创建数组的四种方式,第一种是用直接量来创建数组,剩下三种都是用构造函数来创建数组。怎么说呢,其实用起来的话,还是第一种方法最好用了,也是最简单的一种方式。”
“嗯,说得好,关于数组的创建,叶小凡说得算是比较通透了。”林元青也满意得点了点头。场外弟子又是一阵喧嚣。
“刚才你还说到数组的 length 属性,那是什么?”对面弟子收起了对叶小凡的轻视,皱着眉头问到。
“哦,你说的是 length 属性,数组只有一个长度属性,就是 length,length 表示的是数组所占内存空间的数目,而不仅仅是数组中元素的个数。比如说,我可以定义一个长度为 8 的数组,但是里面却只有一个元素。就好像刚刚的代码。”
var b = new Array(8);
“变量 b 指向一个数组,这个数组所占内存空间为 8 个单位,也就是说,有 8 个位置可以让这个数组存放别的元素。虽然我现在还没有给这个数组添加任何的元素,但是不代表这个数组没有长度,而这个长度就是数组的 length 属性。”
“那这个数组内部到底有什么?”
“这个数组内部就是 8 个空元素,没有东西,但是占据了内存。”说着,叶小凡打出一段代码来验证。
var b = new Array(8);
console.log(b);
代码运行,众人看得清清楚楚,结果是[ <8 empty items> ]。
“原来如此。”对面弟子也佩服起叶小凡来。
“数组作为一个对象,就有着很多内置的方法,接下来就说说那些有趣的方法吧。”
1.16 第十六章 数组方法
“首先是 push 方法,它可以把一个元素添加到数组里面。把数组想象成一个长长的盒子,我如果想要给数组添加新的元素,就可以用这个 push 方法。”说着,叶小凡打出一段代码流:
var b = new Array(8);
b.push("苹果");
b.push("香蕉");
b.push("牛油果");
console.log(b);
“如果直接用 push 方法,那么元素就会被添加到数组的尾部,而且,原来的 8 个位置无法去占用,就直接跟在后面了。”
“怎么会这样,那前面的 8 个位置难道就没有用了吗,这样岂不是很浪费?”对面弟子大感不解。
“用 push 方法确实没有办法做到,但是要利用前面 8 个位置,还是有办法的。就是用数组本身去写一个数据,比如这样。”说着,叶小凡打出一段代码流:
var b = new Array(8);
b.push("苹果");
b.push("香蕉");
b.push("牛油果");
b[0] = "黄瓜";
console.log(b);
“大家请看,数组本身有写数据的能力,只要用数组变量加上一对中括号,然后在中括号里面写上对应的下标位置,就可以给对应的内存空间塞入数据啦。”
“当然了,如果我要修改某一个位置的数据,也是用同样的方法。”说着,叶小凡打出一段代码流:
var b = new Array(8);
b.push("苹果");
b.push("香蕉");
b.push("牛油果");
b[0] = "黄瓜";
b[0] = "西瓜";
console.log(b);
“像这样,下标位置相同的地方,重复赋值,就可以实现修改数组元素的目的啦。”
“那怎么删除数组中的某一个数据呢?”对面弟子又问到。
“删除数据的话,需要用到数组的 splice 方法或者 pop 方法。”叶小凡想了一会,坚定地说道,“先说 pop 方法,这个方法可以删除数组尾端的那个元素。”说着,叶小凡打出一段代码流:
var b = new Array(8);
b.push("苹果");
b.push("香蕉");
b.push("牛油果");
b[0] = "黄瓜";
b[0] = "西瓜";
b.pop(); //删除最末尾的那个元素
console.log(b);
“很显然,刚才数组最后一个位置的元素是牛油果,但是现在已经没有了。pop 方法就是默认删除数组中最后一个元素。可以这么认为,先进入数组的后删除,后进入数组的先删除。刚才我说道,删除数组元素有两种方法,pop 只是其中的一种,还有第二种,就是 splice 方法。**splice()**方法的作用是插入、删除或者替换一个数组元素,他不光会在原有的数组上进行修改,还会返回被处理掉的内容,因此这是一个功能强大,但是不容易使用的方法。**splice()**方法用前两个参数进行定位,余下的参数表示插入部分。”
“上面叫用前两个参数进行定位?”
“比如我现在有一个数组:【1,2,3,4,5】,splice 方法的第一个参数代表需要操作的数组的起始位置,比如你要删除数组中的某一个元素,那么你得确定从数组中第几个元素开始删除。因为数组的下标位置默认从 0 开始,所以加入我要删除数字 3 的话,就需要确认从数组下标为 2 的地方开始删除。然后,spice 方法第二个参数代表了要删除元素的格式,如果我只需要删除一个数组 3,那么我只需要在第二个参数的地方填入 1 即可。”说着,叶小凡打出一段代码流:
var a = [1, 2, 3, 4, 5];
a.splice(2, 1);
console.log(a);
“如果我要删除 3 和 4 两个数字,只需要把第二个参数替换成 2 就可以啦。”
var a = [1, 2, 3, 4, 5];
a.splice(2, 2);
console.log(a);
“那如果要把数字 3 替换成数字 38,并且再在 38 的后面加一个元素 66 咋办?”对面弟子又问道。
“那也简单,我刚才说了,**splice()**方法用前两个参数进行定位,余下的参数表示插入部分。要把 3 替换成 38,思路就是先把 3 删掉,然后再在后面加上一个 38 就可以了。后面要加 66 的话,就再多写一个参数 66。”说着,叶小凡打出一段代码流:
var a = [1, 2, 3, 4, 5];
a.splice(2, 1, 38, 66);
console.log(a);
全场寂静,谁都难以想象,叶小凡不过是一个新人,竟然能够对数组有这么高深的理解。这掌握得岂止是全面了?
过了好一会儿,对面弟子似乎不甘心,又问:“我以前见过有的师兄把数组转换成一种字符串,你会么?”
“你说的是数组的 join 方法吧,join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的,而这指定的分隔符就是 join 方法的参数。比如,还是刚才的例子,我可以把它里面所有的元素转化为用逗号分隔的字符串。”说着,叶小凡打出一段代码流:
var arr = [1, 2, 3];
var str = arr.join(",");
console.log(str);
正当叶小凡还要介绍数组的其他方法时,林元青淡淡地说了一句:“可以了,这一局,叶小凡胜!”
叶小凡三绝连胜,进入决赛!
1.17 第十七章 蒂花之秀
叶小凡甚至都没有出力,就已经轻松进入了决赛,对面弟子能进入决赛,自然也是有着自己惊人的天赋和能力,但是这一切和拥有叶老指点的叶小凡来说,实在是不值得一提。
“现在进入决赛,题目是,函数!”林元青严肃地宣布了决赛题目。
“什么,竟然是函数!天哪,这不是 JavaScript 中比较高级的技术点了吗。真没想到,最后一题是函数啊!”
“是啊,不要说这帮新人,就算是我吗已经在门派中历练了好几年的弟子,也不敢说可以轻而易举地驾驭函数啊!”
场外的大弟子们纷纷唏嘘。的确,函数对于新人来说,确实有点难了,这一点是公认的。其实林元青也没指望这帮孩子能够对函数有多么深刻的理解,只是想看看有没有人知道函数这个概念,若是能够自己编写一个或者两个函数,那自然是再好也没有了。
“老林啊,你这题可是有点难为人啦。”尹曾琪笑着对林元青说。
对面弟子看着叶小凡,抢先开头:“函数是一组可以被重复调用的代码语句,格式是这样的。”说着,只见他打出一段代码流:
function test() {
alert("函数被调用了!");
}
“我刚才就定义了一个函数,函数的定义需要用到 function 关键字,然后空格,加上函数的名字。刚才的代码中,函数的名字就是 test,叶小凡,想必你也看到了 test 函数的右边我还打了一个圆括号,这个圆括号里面是用来放参数的。就是说,函数里面如果需要用到一些外面传进来的数据,就可以通过参数变量来做传递。最后就是函数体了,也就是用花括号扩起来的部分,就是函数的函数体。在函数体中,可以编写多条 JavaScript 代码,当然了,因为只是举一个例子,我的函数里面只是写了一个最基本的 alert 语句,弹出一个窗口罢了。接下来,我需要去调用函数。”
对面弟子发力,又打出一段代码:
function test() {
alert("函数被调用了!");
}
test();
“哈哈哈,叶小凡,看到了吧。这就是函数调用了,函数的调用方法就是函数名称右边加一个圆括号,打一个括号就表示要去执行函数的函数体了。我刚才编写的函数里面只有一个 alert 语句,那么调用这个函数的效果,就是去执行 alert 语句。”
“叶小凡,你认输吧!能够被我打败,也算是你的荣幸了!”对面弟子笑呵呵的对叶小凡说道。
叶小凡听到后,只是微微一笑,不急不躁得来了一句:“很好,接下来轮到我来说了。首先,函数分为七重关!”
全场一片寂静。
1.18 第十八章 函数七重关之一 (函数定义)
“这小娃娃到真是好大的口气,一口气说了函数有七重关,这老夫倒要好好品鉴一番。”尹曾琪目录精光,口气中带着一丝丝嘲讽,却也有一丝好奇。
林元青也被叶小凡的话吓了一跳,全场更是炸开了锅。
“这个叶小凡,说话还真是狂妄,什么函数七重关,我倒要看看他能够说出个什么来!”
罗丹面色凝重,眼睛更是一眨不眨地盯着叶小凡。叶小凡也愣了一下,讲真,这实在是怨不得叶小凡,这函数七重关,可是叶老亲自教导自己的。在平时和叶老的交流中,叶小凡也早已习惯把函数七重关挂在嘴边。谁知道,自己把函数七重关一讲,会引起这么大的轰动!
“好,叶小凡,你就说说,你所谓的函数七重关究竟是什么吧!”林元青也微笑着摇了摇头,对叶小凡说道。
“是,弟子遵命。函数七重关,这第一重关,自然指的是函数的定义。”叶小凡小声说道。
“函数的定义需要用到 function 关键字,定义函数的语法方才这位师兄也已经讲过了,我这边不再赘述。只是,我这边需要提一下的是,除了刚才那位师兄提到的定义函数的方法,其实还有另一种方法。”说着,叶小凡随手打出一段代码流。
//定义函数
function myFunction() {
//函数体
document.write("This is My First Function!<br>");
}
“这是第一种定义函数的方法,也是最为常用的方法。哦,对了里面的 document.write 方法表示用 JavaScript 向页面输出一段话。接下来,我再来讲讲第二种方法。”说着,叶小凡又随手打出一段代码流。
var a = function () {
document.write("This is My Second Function!");
};
“这便是第二种定义函数的方法了,和第一种方法有所不同,体现在第二种定义函数的方法需要先定义一个变量,比如 var a,然后还是用 function 关键字去定义一个函数,再把这个函数赋值给变量 a。因为最后要赋值给变量 a 的,因此这里定义函数的时候就不需要加上函数的名字了,这就是其中一个区别。用这种方法定义出来的函数,函数的名字就是变量的名字,也就是说,我要去调用这个函数,就这样做。”
a();
“哗众取宠,这两种方法本质上有什么区别,不都是一样调用么?”对面弟子对叶小凡的讲解嗤之以鼻。
“这自然是有区别的,刚才我讲了第一个区别,现在说第二个区别,这第二个区别,就体现在函数的调用上。”说着,叶小凡又随手打出一段代码流。
a();
var a = function () {
document.write("This is My Second Function!");
};
“这位师兄,你说我这段代码可以成功调用函数 a 么?”
“你这不废话么,当然可以了!”
“好,那师兄请看。”叶小凡开始执行代码。
**_函数七重关(1).html?hbt=1539409418311:38 Uncaught TypeError: a is not a function at 函数七重关(1).html?hbt=1539409418311:38_
众人看得亲切,之间代码直接报错了!对面弟子瞬间不说话了,场面一度有点尴尬。
“如果是用第一种方法定义的函数,把调用语句放在前面,是可以成功调用的。”
myFunction();
//定义函数
function myFunction() {
//函数体
document.write("This is My First Function!<br>");
}
代码运行,成功在页面上打印出:This is My First Function!
众人一下子又炸开了锅,有的大弟子一脸得难以置信。就连尹曾琪也对叶小凡稍微侧目,场外的罗丹面色则更加凝重。
“导致这种情况的原因是,如果用第一种方法去定义的函数,会被提前加载,因此调用语句可以写在函数的定义之前,因为那个时候函数已经被加载完毕了。而用第二种方式定义的函数,是不会被提前加载的。换句话说,必须要执行到函数定义的语句时,才会加载这个函数,正因为这个道理,刚才的代码才会直接报错。因为在调用 a 函数的时候,a 函数还没有加载啊,那么强行调用一个不存在的函数,自然是不被允许的咯!”
“接下来,我再举一个例子来说明这个情况吧。”说着,叶小凡又随手打出一段代码流。
console.log(a);
var a = function () {
alert("函数被调用了!");
};
console.log(a);
代码运行后的结果,一如叶小凡所言,第一个 a 打印出来是 undefined,表示未定义。第二个 a 打印出来就是具体的函数了,说明这个时候函数 a 已经被加载完毕!
“我有问题,为什么第一个 a 打印出来是 undefined,而不是直接报错呢?”对面弟子疑惑得问到,接着他又侃侃而谈:“我之前遇到过这种情况,就是去引用一个从来没有被定义过的变量,得到的结果是直接报错的!比如…”
console.log(apple);
ReferenceError: apple is not defined
“看吧,这样写的话就直接报错了,因为我从来没有在任何地方定义一个 apple 这个变量。你刚才调用一个还未被加载的函数,为什么是打印出来 undefined 还不是报错呢?”
“是啊,这是为什么呢,看看叶小凡怎么说?”场外又窃窃私语起来。
“你混淆了概念,函数有没有被加载,跟变量有没有被定义是不同的事情,不要放在一起谈论。就比如说我刚才打的代码。”
console.log(a);
var a = function () {
alert("函数被调用了!");
};
console.log(a);
“注意看,函数有没有被加载,可以看成 function 有没有赋值给变量 a。从代码上来看,自然是没有的。因为 console.log 语句是写在 var a = function… 的前面啊。也就是说,调用 a 变量的时候,a 变量并没有被赋值。但是不管 a 变量有没有被赋予一个 function 函数,我就问你一个问题,a 有没有定义?”
“额,这个,定义是定义了,可是…它并没有被运行到啊!”对面弟子一脸的不服气。
“这个就要说到 JavaScript 代码的运行机制了。”叶小凡淡淡地说到,丝毫没有慌乱,而且,在叶老给自己灌输函数七重关的时候,自己也有过一样的疑问。
1.19 第十九章 JavaScript 编译原理
“谈到Javascript代码的运行机制,那可就说来话长了。”叶小凡学着长辈的口吻,一脸的欠揍。
就连林元青都有些看不下去的,笑着说道:
“那你就长话短说吧!”
“是,弟子遵命,先来看一个最简单的例子。”说着,叶小凡随手就打出了一段代码流。
var a = 10;
“叶小凡,你这是再逗我吗,这么简单的代码谁看不懂?”对面弟子感到有些不耐烦。
“师兄,你先别急,没错,这无非就是一个简单的定义语句,可是,你知道它内部的原理吗?JavaScript代码再运行之前,会经过一个编译的过程,而编译有三个步骤。”叶小凡不紧不慢地说到。
“哦,小娃娃,你可好好说说,是哪三个步骤?”尹曾琪也来了兴趣,因为身为掌尊地他,也是头一次听说这个讲法。
“首先第一个步骤是分词,JavaScript代码其实就是由一句句话组成的,分词的目的就是把这些代码分解为一个个有意义的代码块。比如刚才的例子,如果经过分词的步骤,得到的结果就是var、a、=、2、;”
“第二个步骤是解析,由JavaScript编译器去将刚才分词得到的一个个代码块进行解析,生成一棵抽象的语法树(AST)。
这么讲的话有点难以理解,简单来说,JavaScript代码是没有办法直接运行的,要运行JavaScript 代码,就需要由JavaScript 编译器对其进行编译,只有编译之后的代码才可以被识别,然后再由JavaScript 引擎去执行代码逻辑。
但是,由于JavaScript这门编程语言的特殊性,编译的过程一般就在代码执行前的几个微秒之内,甚至更短。所以,直观地看,编译和运行是同时发生的。
或者说,我们根本感受不到编译的存在。就比如刚才的例子。var a = 10; 编译的过程实在是太短太短了,我们根本就感觉不到编译的存在。
但其实JavaScript引擎早在我们运行这段代码的时候,就已经完成了编译,然后立刻就做好了要执行代码的准备。”
“那你说的抽象语法树是怎样的?”
“抽象语法树定义了代码本身,通过操作这颗树,可以精准的定位到赋值语句、声明语句和运算语句。”叶小凡不紧不慢地说到。
“再来说说刚才的代码,很明显,这是一个赋值语句,当然,也是一个定义的语句。我们通过JavaScript的解析器来把它解析为一棵抽象树。”
“让我们一个一个来看,首先是最顶层的大节点,也就是这棵树的顶端,上面清清楚楚地写着Program body,这代表我们写的代码是一个程序。然后再看这个程序里面的第一个,也是唯一的一个子节点,上面清清楚楚地写着VariableDeclaration,意思就是变量声明。哦,这就很明白了,**var a = 10;**这句话是一个程序,程序的目的是做了一个变量的声明。现在,让我们展开这个子节点看看,里面还有什么玄奥?”
“在VariableDeclaration(变量声明)节点中,包含了两个子节点,一个是declarations[1],另一个是kind。declarations[1]是声明数组,中括号里面写了一个 1,表示我们这个语句里面一共只声明了一个变量。Kind代表种类,我们是用var 关键字来声明一个变量的,我想,到这一步,应该没有什么问题。”
“继续展开declarations[1],发现一个VariableDeclarator节点,这个也表示变量声明,正因为上一个父节点是declarations[1],[1]表示里面只有一个声明,因此我们看到展开后里面也只有一个子节点。”
“好,终于看到变量声明的具体信息了,可以看到里面分为id 和 init 两个子节点,id 代表变量名,identifier 是标识符,在这里就代表我们的变量名,也就是a。Init 表示变量的初始化操作,从我们的语句上也能看出,是将文字的 10 赋给 a 变量。”
“如果我把代码换一下,不把 10 赋值给a,看看会怎样?”叶小凡嘿嘿一笑,卖个关子,随后又打出一段代码流,并且用JavaScript Parser解释了一下。
“如果没有给a赋值,JavaScript的解释器也会给 a 赋一个初始值,null代表空。注意,这里的null不要理解为JavaScript里面的数据类型 null,而是语义上的空。实际上,在代码执行的时候,a的值是undefined。接下来,我们来看看如果输出一个a变量会发生什么?”
var a;
console.log(a);
“现在和刚才不同,多了一个console.log输出语句了。在生成的抽象语法树上,又挂了一个新的果实。ExpressionStatement( 表达式语句),表达式语句就是普遍意义上的一行JavaScript代码。console是一个内置对象,log 是 console 对象的一个方法,a 作为参数传入了 log 方法。总的来说,这就是一个函数的调用语句。我们来看下这个表达式语句的抽象语法树。”
“接下来讲最后一个步骤,就是代码生成。在这个过程中,JavaScript 引擎会把刚才第二个步骤生成的抽象语法树做一个转化,转化成什么呢?没错,就是转化成可执行的代码。也许,最终生成出来的就是一些机器指令,创建乐意一个叫做a的变量,放在变量区。然后分配一些内存来存放给这个变量。最后,将数字 10 储存在了a变量所在的地方。”
PS:抽象语法树的创建可以在网站http://esprima.org/demo/parse.html上自行调试和验证。
1.20 第二十章 函数七重关之二 (作用域)
“咳咳,那我接下来继续长话短说了。要回答之前的那个问题,我必须把作用域的概念再说一说。这便是我所总结的函数七重关里面的第二重关。”叶小凡继续讲解,这些概念自己在叶老的教导下早就已经不知道折腾了多少遍。
“首先,作用域如果要深究的话,还是比较复杂和晦涩难懂的,我就用通俗的话来说明作用域的问题吧。在JavaScript中,可以简单的理解作用域分为两种,一个是全局作用域,一个是函数作用域。所谓作用域,就是当你要查找某一个变量的时候,可以在什么地方找到这个变量。这个寻找的范围,就是作用域了。不管是全局作用域,还是函数作用域,都是被定义在词法阶段。词法阶段,就是刚才所说JavaScript编译代码的第一个步骤——分词。所以,词法阶段也叫作分词阶段。关于全局作用域,看一个比较简单的例子。”
var a = 10;
function test() {
console.log(a);
}
“a变量和test函数都是直接暴露在外面,因此,他们都属于全局作用域。而test 函数的函数体,用花括号包起来的部分,则是函数作用域了。没有错,函数的函数体都属于函数作用域。又因为test函数属于全局作用于,而它自己又拥有一个函数作用域,那么这样一来,就形成了一个作用域的嵌套。也就是说,全局作用域里面嵌套了一个函数作用域。函数作用域里面可以访问全局作用域中的变量,但是反过来就不行。比如刚才的例子,如果我直接调用test函数。”
function test() {
console.log(a);
}
var a = 10;
test();
“答案必然是 10,在这个例子中,函数作用域里面的a会先去当前函数作用域里面寻找是否有一个a变量。如果找不到,就去上一层包着它的父级作用域中去寻找。那么,在这个例子里面,不难看出,外面的父级作用域,也就是全局作用域中确实有一个a变量。那么,在执行函数体的时候,就可以访问到外面的a变量啦。但是,如果反过来就不行,比如这样。”
function test() {
var a = 10;
}
console.log(a);
“刚才已经说了,全局作用域包着一个函数作用域,那么函数作用域里面可以访问到全局作用域里面的变量。但是反过来看的话,全局作用域想要去调用里面的函数作用域中定义的变量,却是做不到的。因此当发生作用域嵌套的时候,只能里面的访问外面的,外面的无法访问里面的。而且,需要额外注意一点,那就是作用域嵌套一般是针对全局作用域和函数作用域,或者函数作用域和其他函数作用域而言的。比如,下面这种方式就不算是作用域嵌套。”说着,叶小凡随手就打出了一段代码流。
if (true) {
var a = 20;
}
console.log(a);
代码运行,结果是 20!
“虽然a 变量的定义是写在花括号里面,但是这里并没有函数的出现,因此不算做作用域嵌套。而且我刚才也已经说了,在JavaScript中,只有全局作用域和函数作用域,你可以认为这里的a也属于全局作用域,这样方便理解。既然都是在全局作用域里面,那么console.log方法自然可以访问到同为全局作用域里面的变量 a了啊。”
叶小凡讲得有理有据,饶是林元青和尹曾琪也微微点了点头。也难怪叶小凡懂的比其他弟子多,毕竟,可是有一个老怪级别的叶老在教导自己啊。
“接下来,我把代码换一下。”
if (false) {
var a = 20;
}
console.log(a);
“瞧,我现在把*if 判断中的true改为了false**,那么你说,下面的**a**打印出来是多少呢?”
“这,应该是报错吧,因为定义 a 变量的语句不会执行了啊。”对面弟子想了想,不是很确定地说道。
“错了,你看好。”叶小凡微微一笑,运功将代码执行了一下。得到的结果是undefined!
“什么,竟然是undefined,为什么?”对面弟子惊呼。
“var a = 20;这句话在if 判断中,而if 判断的条件是*false**,所以的确不会执行。但是,执行代码是运行阶段的时候,在代码的分词阶段和解析阶段,a 变量依然会被获取,并且系统会默认给它一个undefined。又因为a 变量不是在某一个函数的函数体中,而是在全局作用域里面,所以console.log 方法依然可以访问到这个a 变量,而且获取a 变量的值就是**undefined**。”
“接下来可以解释之前的那个问题了。”
console.log(a);
var a = function () {
alert("函数被调用了!");
};
console.log(a);
“第一次执行console.log方法的时候,a 变量还没有被赋值为一个函数,但是JavaScript引擎还是会把它提取出来,放入全局作用域中,并且默认给它一个undefined。所以,第一次打印出来的就是undefined。接下来,就是一个赋值语句了。”
var a = function () {
alert("函数被调用了!");
};
“这个赋值语句,把一个匿名函数赋给了变量 a,那么从此变量 a就指向了这个函数,换句话说,一个名字叫做a的函数就已经产生了。这句话一旦执行,那么a就不再是undefined了,而是一个函数。接下来,执行第二个console.log 方法,这个时候a自然是已经有值了,所以打印出来的是一个函数。”
“好,好,好!”尹曾琪连说三个号,显然对叶小凡的实力不吝赞赏,看下叶小凡的眼光中明显多了好几分神采。
“小娃娃,没想到你小小年纪,竟然可以对函数这么了解。既然你提到了作用域分为全局作用域和函数作用域,那么老夫就再来考你一考。”说着,尹曾琪随手一挥,一段代码就显示在了众人眼前。
var a = 1;
function test() {
var a;
var inner = function () {
console.log(a);
};
inner();
}
test();
“这道题,你来回答看看,答案是什么呢?”
“答案是undefined,这是函数作用域里面嵌套了函数作用域,那么最里面的inner 函数中要访问一个变量啊,就会优先从inner 函数里面去找,结果发现找不到。既然当前函数作用域里面找不到,那么就网上翻一层,去它的父级作用域中,也就是test 函数的作用域里面去找,结果发现找到了,test 函数里面定义了一个a 变量,但是没有赋值,那么a就是undefined。既然已经找到了,那么就不会去全局作用域里面找a 变量了。所以,全局作用域里面的a 变量其实就是一个摆设。”
“好小子,回答得不错。那你再说说如果函数有参数传递的时候,会怎样?”尹曾琪点点头,认可了叶小凡的回答。
场外的观众一下子又沸腾了,要知道,在千鹤宗,尹曾琪可是出了名的抠门,每次考试都喜欢各种鸡蛋里面挑骨头,很少会像现在这样去赞同一个弟子。
“弟子遵命。关于函数的传参,其实我是把它归结到函数七重关里面的第三重关的。”
“哈哈哈,好,好,好。那你就讲讲你的第三重关吧!”尹曾琪哈哈大笑,赞赏之意更浓。
叶小凡抬头看了林元青一眼,得到授意后,就开始讲起了第三重关。
1.21 第二十一章 函数七重关之三(参数传递)
“所谓的参数,就是当函数调用的时候会传进来的值,也就是说,我们在定义参数的时候,并不知道调用的过程中会有什么样的值传过来。”接着,叶小凡随手打出一段绚丽的代码流。
function add(a, b, c) {
var sum = a + b + c;
console.log(sum);
}
add(1, 2, 3);
“这就是一个最简单的函数调用,配上参数传递的例子。一般来说呢,函数的名字定义就要让人一看就知道是什么意思。比如我这个例子中,add 函数,别人一看就能够明白,哦,我这个函数的目的是做加法。调用函数的时候,传进去三个参数,分别是 1,2,3,这三个参数分别对应 add 函数中圆括号,也就是参数列表内的 a、b、c 三个变量。在我定义这个函数的时候,a、b、c 三个变量的变量名是随便取的,当然,能够见名知意最好。”
“在刚才的例子中,add 函数的调用需要三个参数,分别是 a、b、c。换句话说,如果你要调用add 函数,就必须要传入三个参数,就像这样。”
add(1, 2, 3);
“函数的调用就是函数名字加上一对圆括号,这样就会去执行函数里面的代码体了,也就是这个部分。”
function add(a, b, c) {
var sum = a + b + c;
console.log(sum);
}
“函数的代码体一般都是用花括号扩起来的,里面就是正常写JavaScript代码便可以了。没写完一句就要打一个分号,这是JavaScript代码的编写规范。现在我们来看一下这个函数的函数体里面都做了些什么事情,首先是第一行。”
var sum = a + b + c;
“sum 是一个新定义的变量,注意了,这个变量是定义在add 函数的函数体内部的,根据作用域的范围限定,这个变量是定义在函数作用域里面的,函数作用域是一个相对封闭的空间,也就是说,外面的全局作用域是没有办法直接访问函数作用域里面的这个sum 变量的。所以说,这个sum 变量只能在这个函数的函数体内被访问到,它也被叫做局部变量。好,继续看,接下来就是一个简单的加法和赋值了。从代码的字面上也可以看出来,就是把 a、b、c 三个变量相加之后得到一个总量,然后把这个总量用等号(赋值运算符)赋给局部变量 sum的。下面一句是一个打印语句,就是把sum 变量在控制台上打印出来罢了。'”
“那如果我在调用函数的时候,就传了一个参数咋办?”对面弟子问到。
“嗯,你说的这个问题,我想,可以把它单独拆分出来看。比如,我定义一个函数,设置了一个参数,但是传参的时候却一个参数都没有传。像这样的情况,和你的问题是类似的。”
“哦?那你说说看。”
“好的。”叶小凡想了一下,便打出一段代码。
function fun(a) {
console.log(a);
}
“这是一个简单的函数,函数名字是我随便取的,就叫它fun吧。这个函数是有设置参数的,参数名字是 a,当然了,这个a到底是什么,是没有限定的,它可以是一个字符串,也可以是一个数字,甚至可以是一个对象,哪怕,是另一个函数,都可以。因为只是测试,所以我只是在这个函数的函数体中写了一条打印语句而已。接下来,我试着要去调用这个函数,而且,故意不写参数,就像这样。”
fun();
“这是一个非常古怪的例子,因为fun 函数明明是要求填写一个参数的,那就是a。可是,在调用函数的时候,却偏偏没有参数传递进来。那么,这按理说是不被允许的,可是当这种情况真的发生了,会怎样呢?也就是说,没有参数传进来,那么函数中已经设置好的参数等于什么呢?试一下,便知。”叶小凡故意卖个关子,然后执行了代码。
结果显示:undefined
“没错了,结果就是undefined。其实,对于函数传参到底是怎么回事,可以把这个例子再次细分。刚才的函数中有一个参数 a,那么这个参数自然也是属于函数作用域里面的,就相当于这样。”
function fun() {
var a;
console.log(a);
}
“为了方便理解,在关键的地方不犯糊涂。函数的参数,可以简单看成是在函数体,也就是花括号扩起来的地方,里面的第一行,定义了一个变量。因为我们并没有给这个变量赋值,所以这个局部变量就是undefined。可以这么说,任何变量在被赋予真正的值之前,在编译阶段都是undefined。或者说,任何变量,不管最终的值是什么,都曾经是undefined。这些函数的参数可以理解为一种预备变量。接下来说说正常的情况,比如我调用fun 函数,传递一个参数 18。那么传参的过程,就相当于是给预备变量赋值的过程。如果没有传参,那么预备变量自然还是undefined了。再回到刚开始的例子,如果我直传一个参数。”
function add(a, b, c) {
var sum = a + b + c;
console.log(sum);
}
add(1);
“这种情况,a的值是 1,b 和 c的值就是undefined,那么数字 1 和两个undefined相加会是多少呢,真是有意思的问题。结果是NaN,代表无法计算。没错,如果真的那样做,那么就是没有任何意义了。最起码,在这个函数中,那样的做法是毫无意义的。”
“好吧,那如果我多传一个参数会怎样呢?”对面弟子又来一个问题,大有一副不把叶小凡问道誓不罢休的意思。然后,令他没有想到的是,叶小凡立刻就有了回答。
“你说的这个问题,其实也可以单独拆解出来。就好比我定义了一个函数 fun,没有参数,但是如果我在调用fun 函数的时候,故意给它加了一个参数,会发生什么?比如,像这样。”
function fun() {}
fun(10);
“结果是可想而知的,自然是什么都不会发生啦。再回到刚才的例子中去,就算你强行加了第四个参数,对结果自然也不会有什么影响的。”
function add(a, b, c) {
var sum = a + b + c;
console.log(sum);
}
add(1, 2, 3, 4);
“如果我一定要在函数里面访问到额外的参数咋办?”对面弟子一副问到你山穷水尽的气势,就连场外的某些弟子都看不下去了,心想这种问题实在是有些欺负人了。可是林元青在听到这个问题后,却不动声色地望向叶小凡,眼神中隐约流露出一份期待。
“这是可以办到的,其实所有的参数都会被装载到函数内部,一个叫做arguments的数组里面。比如这个 add 函数,虽然参数设置了 abc 三个,但是在函数的内部还维护了一个arguments 数组。我可以用代码来验证。”
function add(a, b, c) {
console.log(arguments);
var sum = a + b + c;
console.log(sum);
}
add(1, 2, 3, 4);
“可以看到,你传过来四个参数,其实就放进了这个默认的arguments 数组里面。换句话说,参数列表里面的 a、b、c 也是根据这个数组来赋值的。现在,我把代码改写一下,就看的更清楚了。”
function add(a, b, c) {
console.log(arguments);
a = arguments[0];
b = arguments[1];
c = arguments[2];
var sum = a + b + c;
console.log(sum);
}
add(1, 2, 3, 4);
“嗯,根据这个特性,可以完成一些有趣的功能,比如,我可以编写一个函数,参数个数任意写,实现数字的累加。说得简单一些,就比方说,我调用 add 函数的时候,传入 3 个数字,那么就进行三个数字的累加。如果传入 5 个数字,就进行 5 个数字的累加。也就是说,不管你传入多少数字,我都可以给你实现一个累加。这便是一个非常灵活的累加器了。”说完,叶小凡一遍思考,一边开始写代码。
“因为实现我并不知道会有几个参数传进来,所以干脆就不设置任何参数了。”
function add() {}
“这样,我先假设调用 add 函数的时候,最起码会有一个参数传进来。那么,先用arguments 数组获取一下第一个位置的元素。如果第一个位置的元素都不存在,那么就返回 0。”
function add() {
if (!arguments[0]) {
return 0;
}
}
“接下来,因为不知道究竟会有多少个参数,也就是说arguments 数组的长度也许是未知的。但是,arguments既然是一个数组,那么就会有length 属性,这个属性里面放的就是arguments 数组的内部元素个数了。这样看来,虽然我不知道会有多少个参数传进来。但是我们在函数的函数体中却可以通过arguments 数组的length 属性预知未来传入参数的个数。这样的话,我就只需要做一个简单的数组循环,就可以了。然后,将所有的数据累加起来,像这样。”
function add() {
if (!arguments[0]) {
return 0;
}
for (var i = 1; i < arguments.length; i++) {
arguments[0] = arguments[0] + arguments[i];
}
console.log(arguments[0]);
}
add(1, 2, 3, 4);
“这种写法的思路就是,从数组的第二个元素开始,往后所有的元素全部累加到第一个元素上去,就得到了数组中所有元素的和。当然,我也可以在函数内定义一个局部变量 sum,全部累加到sum 变量上,比如这样。”
function add() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum = sum + arguments[i];
}
console.log(sum);
}
add(1, 2, 3, 4);
“这样的话,数组的循环就可以从下标为 0 的地方开始。因为如果第一个元素都不存在,那么arguments 数组的length就是 0,也就是说,for 循环连 1 次都不会进去的。函数的结果就是sum依然是 0,还是符合预期。”
“最后,再说一下函数的返回值。就好像之前的例子,sum 变量虽然是所有参数的总和,但是这个sum 变量毕竟只是在函数的内部。根据作用域的关系,外面是没有办法访问函数作用域里面的sum 变量的,可是既然这是一个累加函数,那么外面调用这个函数的目的自然就是要获取累加之后的值。因此,将局部变量 sum暴露出去是非常有必要的。那么,怎么才可以实现这一点呢,方法就是用return 关键字,将函数中的某一个数据返回出去。比如这样。”
function add() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum = sum + arguments[i];
}
return sum;
}
“把sum 变量 return 出去,那么调用函数之后,函数就返回了一个sum出去,也就是说,函数的调用结果就成了sum 变量,外面的全局作用域就可以获取函数内部的数据了。比如这样。”
var sum = add(1, 2, 3);
console.log(sum);
“这便是函数七重关之第三重关了。”听完叶小凡的讲述,就连林元青也点了点头,表示认同,对面弟子也再也挑不出刺来了。