《Eloquent JavaScript 3rd》笔记

前言篇

计算机是人思想的一部分,逻辑、秩序、规则…… 不常见的任务须要额外的编程才能解决。git

机器很死板,无趣。程序员

忍受机器后能享受到便利。express

借鉴了天然界以及人类的模式到编程的设计上。编程

编程的规则原理语法很简单,但创造出知足复杂世界的需求会很难。api

学习中的有些困难和痛苦势必要的。这样以后的学习就更容易了。数组

不要妄自菲薄,换换方式,或者歇一歇,而后坚持学习。浏览器

计算机结构是庞大的,找到围绕目标的所需的知识,理解运行原理。安全

计算机很蠢,可是优势是很快。bash

控制计算机解决问题,就是掌控复杂度的艺术,掌控很差就崩了。。服务器

新的问题每每用旧的眼光和方式解决很差,或者不够好。

不断的实践出错优化才能融会贯通各类状况下的考虑,而简单的阅读却没办法作到。

语言怎么起做用的

从最底层到最接近问题,编程语言存在着不一样层面的抽象,有时候细节须要忽略,有时候又要操纵细节,这方面作得最好的是C++,而JS是主要面向浏览器的。它的不少抽象层级都面向浏览器的应用而不是性能,底层,等等。

干同一件事的代码能够写成不少样子,是更易阅读,更省性能仍是写代码用的时间最少(这是人和公司最宝贵的东西,除此以外才是金钱花费),取决于你的需求进行平衡取舍。

什么JS

JS最先是写浏览器交互、特效之类的小脚本。

JS与Java有关,否则为啥不起别名,但仅仅是为了蹭市场名声,技术实现上上大抵都无关。

JS在编程语言中很烂,但它面向新手,使用的人多了,一次次版本更新也变好起来了。曲线救国,人多才是王道。

JS历史不谈,2015年后ES6版本,每一年都有更新,因此快更新浏览器与时俱进吧!

JS还在MongoDB中能够做为脚本查询语言、Node.js服务器语言。

编码

不只要学,还要写出来。这是一门工程学科,实现是根本,学是为了实现的更好。 Don’t assume you understand them until you’ve actually written a working solution.

全局概览

语言,浏览器,Node.js。也就是能干什么,在哪里干,还能够在哪里干。

值,类型,操做符

程序的表面之下是庞大的代码支撑着

计算机中只有数据,数据的信息能够被解释为控制指令、文件内容等。

二进制的bit,binary digit,只有0和1的序列,表明着各类信息。

例如数字13的二进制表示。

0   0   0   0   1   1   0   1
 128  64  32  16   8   4   2   1
复制代码

易失性内存:8g -> 687 1947 6736 bits,687亿个比特(吓了一跳吧~)

管理如此大量的比特们,须要进行分模块处理,否则会迷失混乱。values, 编程语言中的各类值来划分不一样含义的比特信息,值多了抽象出类型,具备一样的特色,细化下去就是值的不一样,再向上抽象就是类型的不一样。

计算机是复杂的,内存也是复杂的,但人的生命是有限的,没办法了解全部的细节。(这是全部矛盾的源头)

  • 但在编程语言中只关心重要的抽象,省略掉不关心的部分。
  • 好比我须要一个变量来放今天早餐花费了多少钱
    • 关心的部分:
      • 一个是名字花费cost
      • 一个是金额6
      • 合起来就是let cost = 6;
    • 不关心的部分:
      • 上面那行命令怎么从键盘到CPU传递
      • CPU怎么理解6cost绑定的值,仍是这行命令执行6次。
      • CPU把6给我存在哪一个地方
      • 这个6能够共享给其余变量,仍是不能重复利用的的独一无二的
      • cost怎么和6进行的绑定……
    • 事实上是规定不能关心么,不,你能够无聊或者感兴趣研究它到底在某一步干了什么,毕竟种族中随机的变异致使人多了就会有小部分人这么执着于每一个地方,关键是大部分人的时间都很宝贵,咱们在时时刻刻变老,人脑处理的信息是固定的,而计算机的发展是无数前辈智慧的结晶,咱们能用一我的的一辈子去了解这么庞大的知识?不存在的,只能挑重点。

数字

number, 64bits

  • 不一样信息的表示共有2^64种,每建立一个数字的变量,计算机会申请这么大的空间,换成十进制有20位,通常是不用关心会超过申请空间(overflow)。
  • 3.14
    • 浮点数表示只会把小数的字面值精确到某位存储起来,不会彻底复原小数,因此判断时和标准比较差值,
    • 如我想要一个数是3.14偏差在0.01,那么判断if((x-3.14)<0.01), 而不是 if(x==3.14)
  • 314 能够表示为3.14e2, e(exponent)

数字通常还要分出一位用来表示正负,若是还要表示小数就要有更多位的损耗,有各类表示小数的方法,你看想要完成更多的功能,就确定要付出一些东西。

算术

数字的主要功能是用来算术,因此这些东西不是被凭空发明出来的,而是实实在在有用才会出如今JavaScript中。

  • 二元操做符:+ - * / %

  • precedence: * / % > + -

  • %: remainder operator

    • 除余,咔嚓咔嚓除完剩下的,多好理解,比死背强记简单多了
  • 改变默认优先级:括号

3种特殊数字

  1. Infinity, -Infinity
  2. NaN: not a number

字符串

  • 好宽容,三种符号可表示字符串,没有character类型
    1. backticks,反引号中的string,也叫template literal
    2. 换行符不用进行转义就能够保留
    3. half of 100 is ${100 / 2}
    4. 计算${}
    5. 转换成字符
    6. 被包含
    7. ''
    8. ""
  • 转义字符:backslash,\
    • 意味着这个字符以后的应该特殊对待,
  • 字符串中的每一个字母占16bits,小于Unicode数量,因此有些字符用占2个字母的空
  • 只有+操做符:concatenates

一元操做符

操做符除了用符号表示,再多了符号不够用命名关键字表示.

操做符的操做数能够是一个,两个……

一元操做符:

  • typeof: return a string of type of the operand

符号的一词多义:

  • 缘由:由于键盘的符号有限不够用,有的符号又当爹又当妈
  • -(2-1):
    • 第一个-,unary operator,操做的对象是一个,即2-1的结果,语义是将一个数置负,
    • 第二个-,binary operator,须要两个操做对象,语义是有两个数,他们想减

布尔值

信息的存储最理想状态是e次方,即2.718,而现实离理想还有段距离,因此目前的计算机是基于二进制的,实际上三进制更理想。不过二进制是第二好的选择,比十进制不知道高到哪里去了。

既然是二进制,就免不了这个二的状态由哪两个表示,计算机说是0和1,实际上在不一样器件里面的表示,有用电平高低的,有用正弦不一样的,程序利用truefalse,日常咱们说话用有和没有,是或者不是,开或者关,若是是三进制那还包括一种状况,那就是不知

比较

比较的结果是个布尔值

字符的比较是按照ASCII码以及Unicode码来的,而不是字典中从A到Z的顺序,注意Unicode字符是ASCII的超集,而后ASCII中的编码在Unicode中大小是相同的,所谓的兼容ASCII,无非是前面多8个0。

只有一种值不等于它本身: NaN,它的含义就是用来表示无心义的结果,因此他也不和其余无心义结果相等。Oh,shit,难以理解。。

逻辑操做符

语义:注意它的操做对象是布尔值,不是用来算数的,尽管数字0和1会被变量提高转换成true 和 false。

  • and: &&
  • or: ||
  • not: !

precedence: > == > && > ||

一个三元的逻辑操做符:true ? 1 : 2, ternary, conditional operator, question mark and a colon

三元操做符短路,意味着有些语句不执行。

Short-circuiting of logical operators

短路,左边算完了若是返回左边,右边就不验证了,称short-circuit evaluation.

||左边true就返回左边,false就返回右边。 &&左边false就返回左边,true就返回右边。

我从未据说过布尔逻辑的处理是这样的,什么叫and运算?左边false,就返回左边对象?为何不是返回false?我要你这个运算何用?这种神奇的功能我本身八辈子都用不到

垃圾语言,奇葩设定,毁我青春,****。

WTF为何不和C++,Java同样?

Examples of expressions that can be converted to false are:

null;
NaN;
0;
empty string ("" or '' or ``); 
undefined.
复制代码
  1. ||先把左边转换成布尔值,是个数就返回左边,不是个数返回右边
  2. 转化规则
-  `0`, `NaN`, `""`, `null`, `undifined`会被算做 `false`
- 其余的全部算做`true`
复制代码
  1. true 则返回左边
- `console.log('bat||'ant')  // -> bat`
复制代码
  1. false 则返回右边
- `console.log(null||'hi')  // -> hi`
复制代码
  • 利用这个特性,能够将多是空值的变量加上||,使之成为备胎。

空值

设计上的委曲求全,唉,但是Rust语言不火啊,人们并不须要正确,人们须要兼容稳定能用。

  1. null
  2. undefined

自动类型转换

JS老是喜欢能处理你给的各类值,也就是对你很包容,但这对于精确的控制却很差。语言的设计

type coercion

  • null*8 -> 0

  • "5" - 1 -> 4字符拼接优先于数值计算。

  • "5" + 1 -> 51

  • NaN若是一旦产生,那么与此相关的结果仍是会NaN

  • 自动类型转换和严格相等

    • ==!=
      • same type
        • 除了NaN
      • diff type
        • nullundefined之间为true,和其余为false
        • 其余类型自动转换
    • ===!==
      • 不包括自动类型转换

程序结构

表达式和语句 expressions and statements

砖块在高楼中才能体现更大的价值。意思是良禽择木而栖才能发挥更大价值?

可以产生一个值的代码块称之为表达式,expression。字面值,带括号,运算式都是expression

expression之间互相组合、嵌套组成了语义更复杂的expression。

expression是某一小段子句,而语句,statement是一句完整的话。程序就是一列语句们的集合。

最简单的statement是一个expression加上一个分号。(可忽略的分号。。。)

expression产生一个值,而后被周围的代码所利用。

而statement只立足于他本身,除非指定和其余东西有交集。当改变了屏幕上的文字或者改变了计算机内部的一些状态,以至于影响后来的语句执行的结果,这就叫作影响,effects。像1;这种语句确实改变了了计算机内部的东西,但对于别的代码没有明显的做用。

分号这个东西大部分时间可加可不加,可是你懂的总有意外。有时候不加分号会让会让两行代码变成互相影响的一句statement,为了安全、不找麻烦,仍是加上吧,否则要认识不少哪些是必须加分号的复杂状况。

绑定 bindings

表达式会产生一个值,好比1+2,可是产生的结果3,若是不马上使用它,或找一个空间分给他,它立刻就不见了。

let caught = 5 * 5;

variable or binding

keyword: let, 声明+赋值

  • 声明了caught,就是宣布要有光!,而后计算机内存里面就找到一个地方对应着这个caught.
  • 赋值,右边5*5的结果绑定到了caught上。
  • 只声明没赋值的状况为undifined
  • 以后再次出现caught,它就会被要么做为空间地址,要么取它的值25
  • 一次声名,屡次绑定。
  • 绑定更像触手同样能够多个绑定指向同一个值。这句话很费解,是指变量只是个引用,这个值是共享的?仍是说绑定的值和引用的位置是分离的?

var是个历史遗留问题,const一次声明赋值为常量,鞠躬尽瘁,死然后已。

Binding names

字母、数字、$_,不能以数字开头,不能用关键字,不能用保留字。

其实就一个原则就行了,字母开头,各类驼峰表明变量、函数、仍是类。也别起什么各类奇葩名字,没事找事。

Environment

程序启动的时候,环境就激活了语言自己的一部分绑定,以及提供交互的一些绑定。

Functions

默认环境提供的一些变量类型为function.

调用、请求函数的执行,称invoking, calling, applying.

回想数学f(x)=y+1;函数要有输入x,用括号来表示。称arguments, 这个参数可能(x,y),也能够是(x,3,4)

console.log function

console.log 不是一个绑定,它包含了一个句号,对吧,变量名是不容许的,那它是什么呢? console是一个绑定,.log表明检索这个绑定的一个名为log的property。

Return values

side effect: 函数的主要做用是用来返回一个值的,那么显示一段文字,弹出对话框叫作反作用。(这是函数定义,不是写代码的目的。。)

因为函数要return一个结果,因此它符合一个expression,也就是能够和另外的expression组合嵌套在一句statement中。console.log(Math.min(1, 3)+1);

Control flow

straight-line

prompt中输入的值为string,用Number()转换成number进行计算,其实不用人工转化下一行计算的时候会自动转换类型的。。

conditional execution

原先一条路走到黑,如今变成二选一,而后继续走下面的语句。

if (1 + 1 == 2) console.log("It's true");

通常状况下代码要写的让人看着方便,大部分都要加大括号,除非一行简单的if。

多重嵌套的时候每次当下判断都是二选一,注意合并简化

while & do loop

2^10 (2 to the 10th power)

do while,for 和 while(){}的区别就是语义上的

  • 最少干一次用do while
  • 先判断条件再作就用while
  • for和wihle大部分等价
    • 将三个东西做为一个总体划分比while强一点。哪三个东西呢?第一个初始化条件就是while以前的语句,第二个判断条件就是while的判断条件,最后一个执行语句能够放在while执行语句的最后。
    • break时都是直接退出
    • 小部分的区别在于continue
      • while会直接跳事后面代码从新判断,
      • for会直接跳事后面的代码,执行第三条语句后再去从新判断。

缩进空格、换行

纯粹为了易读性,一个仍是两个空格、一个仍是多个换行都不影响它的逻辑。但换不换行是有区别的,参考加不加;的各类规则。

for循环

明明有while为何还要发明for呢?由于经常使用啊,由于代码逻辑惊人的类似重复的部分人就愿意把它封装成新东西,轻轻一挥,魔法就实现了,多好。

for和while的区别就是while常常须要一个循环计数器,而且每轮循环都必需要改动这个循环计数器,再加上原本的条件判断,这不就是for啦?

break continue

for中不设循环终止判断,就能够把判断挪到循环体中if(){*; *; break;}中,这样的好处是判断完以后能够继续在for的环境中执行一些语句。也就是说若是有if(){break;},考虑是否能够放在for语句头中?

break 若是用原来的机制实现,至关于在原代码前加了个if,而且添加的一个分支只有一个改变循环条件、而且还要抵消for第三个语句(若是在for语句中)。你看现在你想要的,一个break就能够解决了。

continue表明着跳过这句以后的循环体中的代码,执行for第三部分(若是在for语句中)。而后继续下一次循环

continue 若是用原来机制实现,至关于原代码前添加if,而且添加的一个分支什么都不作,而且还要抵消for第三个语句(若是在for语句中)。

这两个一个是直接跳出全部循环,一个跳出当前这一轮循环。

updating bindings succinctly

当一个变量的变化是由原来的本身进行更新的话,能够直接在赋值的时候指定什么操做

counter = counter +1;

// 更简便的自我更新

counter += 1;

// 对于自个人加一减一,还能够缩写

counter ++;

// 在Java中 a++ 和 ++a 实现不同,返回的对象是一份原a对象的拷贝对象,因此值和原a同样,后者返回的是加完的a对象,因此相对于原a值加一。在JS中还不知道啥实现

复制代码

switch

注意default:break:,虽然不少时候工具会自动猜想你的意图,帮你补全一些不严谨的逻辑。

一个多状态的值 --> 多重if的代码的简写模式 --> 就是switch。。经常使用的东西才会被发明。

多对多的怎么办呢?用函数封装一下,多个输入值,每一个值有多个状态,随你所愿的组合判断,

Capitalization

  1. 全小写不易读
  2. 下划线,打字多太累
  3. 全大写是函数绑定的构造器
  4. 驼峰风格是惯例。。

一切都是有缘由的,可是全部的事咱们真的须要知道缘由?

注释

有时候代码并不能层次鲜明的由浅入深,由全局到定位局部信息的导航功能,这个时候须要注释来快速定位一大坨只有一个名字的代码究竟是干什么的。

VS Code中快捷键Ctrl /能够快速判断当前是HTML仍是CSS仍是JavaScript仍是JSX代码进行插入相应的注释。

注意多行嵌套没有实现(不是不能实现)交错的处理。以及 //右边全算做注释

练习

Looping a triangle

Q1:

#
##
###
####
#####
######
#######
复制代码

A1:

for (let i=0, j=""; i<7; i++) {
  j += "#";
  console.log(j);
}
复制代码
  • i仅表明从第1到第7行,而这每一行打印出几个的井号跟i无关(至少在这题里面,有的题打印的星星数量可能跟行号有数学关系),这7行每行打印出什么跟j的自加有关。
  • 固然这两行代码由于每次迭代都执行了,能够跟i++放在一块儿,看我的喜爱了,放在那里太挤太丑了不是么。。
  • 仅从该题目的话,这些代码是合格的,但若是考虑扩展性(好比用户指定打多少行、每行的#的数量表达式),效率(是一次性打印,仍是逐行打印)等等,就会有不一样的解法

Q2:

print number 1-100, 3的倍数用Fizz代替,5的倍数用Buzz代替,如果3和5的倍数,用15代替
复制代码

A2:

for(let i=1; i<=100; i++) {
  let result = new Array();
  let string = "";
  let number = i;
  if(i%3===0) {
    string += "Fizz";
  }
  if(i%5===0) {
    string += "Buzz";
  }
  result.push(string||number);
  for(let i of result) {
    console.log(i);
  }
}
复制代码
  • if 没有else的时候,要注意else和if以后的语句不同,由于else是作过判断的,跟判断有关就加个else,跟判断无关,就不加else,直接在后面写。
  • i表明着迭代多少次,number其实能够直接用i不用新建,由于正好题目是1到100,但若是是100到200呢?这就须要将打印的次数和打印的数字区分开,虽然有时候他们值相等不用新建变量,但他们语义是不同的!
  • 首先3和5的倍数是两个原子不可拆的操做,而15不是,因此再添加分支判断15就很浪费,而15实际上就是3和5字符的相加,因此每一步字符是加操做。其次根据JavaScrit||的奇葩设定正好帮助返回FizzBuzz或者备胎数字,纳爱斯。
  • 将全部结果的数据存到了数组中,不只合乎打印出的结果,更容易往后其余操做,虽然只是一道题,不须要啥扩展性 Q3:
Chessboard
 # # # #
# # # # 
 # # # #
# # # # 
 # # # #
# # # # 
 # # # #
# # # #
复制代码

A3:

// 若将该功能封装为函数,下面就是输入的参数变量,n阶矩阵,两种要打印的符号
  let size = 8;
  let symbol1 = " ";
  let symbol2 = "#";
// 横向重复的最小单元,要进行O(logn) 替换掉 O(n)的重复字符的复制
  let symbolSum = symbol1 + symbol2;
  let contentOddAddon = size%2==1?symbol1:"";
  let contentOdd = repeatCharBinary(symbolSum, Math.floor(size/2)) + contentOddAddon;
// 求偶数行的内容,由奇数行内容转换,踢掉第一个,再根据最后一个添加适当元素
  let contentEvenArray = contentOdd.split("");
  contentEvenArray.shift();
  contentEvenArray.push((contentEvenArray[contentEvenArray.length-1]==symbol1)?symbol2:symbol1);
  let contentEven = contentEvenArray.join("");
// 纵向重复的最小单元,要进行O(logn) 替换掉 O(n)的重复字符的复制
  let contentTwoLine = contentOdd + "\n" + contentEven + "\n";
  let contentOddLineAddon = size%2===1?contentOdd:"";
  let content = repeatCharBinary(contentTwoLine, Math.floor(size/2)) + contentOddLineAddon;
  console.log(content);
// 重复某个字符的nlog函数
  function repeatCharBinary(char, n) {
    // 这个地方须要进行重复计算字符,能够将代码封装为一个函数
    // 线性复制char更改成指数级复制
    // 将十进制n转换为二进制,除2取余,对应一、二、四、八、16的权重
    // 余1则存在,那么将结果加上对应的权重数量的字符串,余0则表示不存在,那就不加这位的字符串。
    let tmp="";
    while(n!=0) {
      if(n%2==1) {
        tmp += char;
      }
      n = parseInt(n/2); //结果从低位算起,余1则存在,结果加之,继续除2余1,算更高一位
      // 以前算每一位的权重是否存在,这一行是算若是存在,对应的字符串长多少,每轮要变为更低位字符串的两倍。
      char += char; 
    }
    return tmp;
  }
复制代码
  • 题目两种不一样的符号,交叉成字符串,宽n,高n,奇偶互异,思路是
  1. 计算出第一行的内容
  2. 模式就是两个字符的屡次重复
  3. 偶数n/2次
  4. 基数n/2次+第一个字符
  5. 这么屡次的重复采用8421的指数翻倍相加?
  6. 第一行内容队列一进一出造成第二行内容
  7. 第一二行做为一个重复的最小单位,而后屡次指数重复输出
  8. 取整要用Math.floor(),默认的取整不知实现机制,不过老是少一个,很奇怪。

Function

搞计算机的是天才么?不是的,你们只是站在巨人的肩膀上,用别人生产的砖块垒起本身想要的一面墙。

函数是个很重要的内容。它将一大块代码浓缩成一个名字。这样能够分解一个大问题的庞大代码,变成几个小问题的代码,只抽象出变化的部分,称之为输入,而后输入参数进入一样逻辑的代码中,产生不一样的输出。而仅仅只须要用一个函数名字就能够关联变量和逻辑。

优秀的文章常常经过不一样的词汇乃至精妙绝伦的句子来体现文章的艺术,而编程不一样,它只在意能干成什么事,因此可能不那么动听引人入胜。

编程中的语言就像公式同样枯燥,但若是懂了就明白它的精确和简洁,不一样于文章给人精神上的享受,相似的帐单也很枯燥,但它精确简洁,而且你须要它的时候,他能知足你。

defining a function

let area = function(x,y) { return x*y;}
复制代码

能够经过绑定到一个变量来重复调用这个函数。

函数功能

  1. 输入
  2. 能够没有
  3. 输出
  4. 没有return,执行完默认return 一个undefined
  5. return
  6. return; 则return 一个undefined
  7. side effect

bindings and scopes

做用域只在当前以及子做用域

  1. Global
  2. Local bindings
  3. 函数的每次调用都会新建一个实例
  4. ES2015前,只有函数能建立新做用域(好比if for 都默认是global)
  5. ES2015后,JS的做用域终于和C++Java等正常语言一致了。。

nested scope

多级嵌套,lexical scoping

Functions as values

let hi = function() {

};
复制代码

新建变量的函数绑定,要加分号。

函数绑定的名字呢,还能够被绑定为别的东西,因此变量并非函数,他只是个指示器,表明着它可能指向一个数字、字符串、或者是函数等。

declaration notation

声明式的函数标记, 仅仅声明,不进行调用

function hello() {}
复制代码
  1. 能够不用加分号
  2. 这种函数在执行的时候会挪到当前做用域的最开始部分优先于其余代码

arrow function

let hey = (x, y) => {

};
复制代码

省略了函数名,而后直接将输入指向输出。

const square = x => x*x;
复制代码
  1. 只有1个变量的时候能够省略括号
  2. 只有一行语句要return的话,能够省略大括号。 涉及到知识点
  3. 递归
  • 要有终止条件
  • 为了学习语法的demo并不表明最优解
  • 既然出现递归和循环均可以干的事, 那么说明他们有擅长的方面, 否则没价值的东西不会出如今语言中,可是对于某个问题可能只有其中一种是最好的.
  1. 边界条件, 特殊值的覆盖
  • 关键要找到典型的例子进行覆盖测试.
  • 若是例子不用典型表明, 测试的次数太多.
  • 若是例子选出来了但表明不典型, 覆盖的范围太少.
  1. 有问题先Google
  • 那里是广阔的海洋, 数不清的金银混杂着狗屎, 但通常而言被筛选到第一页搜索结果的都是上好的金子.
  1. 默认参数
  2. 箭头函数
  3. for中的i,默认为循环计数器
  • 若是用它作了别的含义,最好新建变量,使之修改的时候更容易
  1. 统一的函数结果的返回接口, 容易修改和阅读
  • 但在屡次递归中直接无论进入到哪一个分支中都进行return, 是否是语义更清晰些?少绕一步赋值给结果,再return出去.
  • 可是若是是复杂的大量代码有利于定位return值的变化
  1. 拼写错误, WTF!
  2. 再小的代码, 实现起来也是要费些周折, 不要忽略实际问题而想固然
  3. 语法是会慢慢进化帮助开发者的, 好比最后一个参数以后也能够加,, 方便往后直接添加.
let power = (base, exponent=2) => {
  let result;
  if(exponent===0) {
    result = 1;
  } else {
    result = base * power(base, exponent-1);
  }
  return result;
};
console.log(
  power(0),
  power(1),
  power(0,3),
  power(2,1),
  power(2,2),
  power(3),
  power(100,3),
  power(100,100),
);
复制代码

the call stack

函数执行完会把return的值返还到调用它的地方,而后继续执行接下来的代码。

call stack: 函数调用的栈

  1. 每调用一个新的就会push进一个新的函数执行完再pop出去
  2. 这个栈是占内存空间的,若是调用、递归的函数太多,栈会溢出

optional arguments

给一个函数多个参数,而函数只要一个的时候,它就取一个而且继续执行。

而若是给的函数不够,那么默认undefined

怎么样JS是否是很混蛋?若是程序员无心写错了,他都不报错?

那若是有意写成这样,至关于C++ 中的函数重载。

function minus(a, b) {
  if (b === undefined) return -a;
  else return a - b;
}
复制代码

哇,好牛逼。。这样居然实现了减法。。一个数就是减本身,两个就是互相减

默认参数, 另外指定了则按指定的,无指定的按默认的

funcition area(redius, PI=3.14) {

}
复制代码

closure

ES2015后改成了正常编程语言的做用域规则。chain scope,子能够看到父,父不可看到子的变量。 ES2015以前只有function有本身的做用域。

ES2015以前定义的var关键字最好用let, const代替。 若是没有关键字直接hi="hi",默认是全局做用域

内部做用域能够调用父做用域的变量,那么若是父做用域想调用子做用域呢?

让父函数新建一个子函数,并return这个子函数,子函数能够访问它爹,而后新建一个变量承接父函数,这个变量就绑定到了return回的子函数。

若是这个变量是全局的,那么这个子函数不会回收,因此它爹也不会回收。

一样不用var新建的变量若是绑定到了一个函数,也是全局变量,不会回收。

因此注意垃圾满了。尽可能不须要不要用闭包。

这样引用一个函数实例的一个子函数,称之为一个闭包。

recursion

函数调用本身叫作递归,递归要有终止条件return出去.

  1. 递归实现比for 更慢3倍。。由于调用函数须要的资源更多。
  2. 注意栈溢出

优雅和速度老是颇有趣的矛盾体。。。更快意味着可能只顾目的,吃相很差看。更好看,可能顾及的就多,妨碍了核心目标。因此以切都是权衡,哪些必需要快不顾长相,快到什么程度?一样相反。

一个程序的项目实现的地方有不少,市场、宣传、技术、组织。而技术之中也有开发效率、机器效率、用户友好、利于维护等一堆互相矛盾的点,要抓住主要的目的,势必就要在其余点上妥协。你不可能用最快的开发速度,写出最省机器资源、最利于维护扩展、最对用户友好的代码。

机器的快速发展,因此程序语言愈来愈高级,库愈来愈顺手,你若是总是担忧这个地方性能很差,那么你想一想你有没有由于自由的呼吸空气而以为浪费,有没有以为本身跑步呼吸了更多的空气而浪费?为何?由于空气与你的呼吸相比很廉价,一样在机器相对于人的时间很廉价。因此那段代码利于你快速理解阅读、而且最快的知足功能,为何要去省下几口空气呢?除非你掉进了水里(机器运行吃力),当空气(性能)真的很重要的时候,再去考虑空气(性能)。

甚至不少你觉的必定要优化的东西根本不值一提, 就像你多呼吸了两口空气同样可有可无.你收货的只有沾沾自喜觉得赚到了, 但你失去的是你最宝贵的生命的几分钟甚至几个小时.

还有不少时候你所谓的优化在优化编译器的专业大师前不值一提, 它甚至原本能够大幅度自动优化的地方,被你用生涩的奇技淫巧仅仅优化了一点.不要自做聪明, 衡量你生命的付出和换来的产出而不只仅是沾沾自喜.

求从1开始, [+5]或者[*3]获得指定数的方法

递归在多分支的时候实现起来比循环更好。

  1. 注意递归终结条件, 只能是成功或者找不到, 而不是最短路径.
  2. 本质就是暴力尝试...只不过用递归写起来简单.
  3. 只有反引号里才能透明写string模板, ${ variable }添加变量.
  4. 两个参数一个做为计算, 一个做为输出展现. 这是两个信息, 毕竟没办法打印一个算式的过程? 目前孤陋寡闻...
  5. 运用递归要找到问题中能转化成初始条件, 层层相扣重复的逻辑, 以及终结条件, 不要用过程来思考, 容易卡死..
let findIt = target => {
      function find(current, history) {
        let result;
        if (current === target) {
          result = history;
        } else if (current > target) {
          result = null;
        } else {
          result =  find(current + 5, `(${history} + 5)`) || 
                    find(current * 3, `(${history} * 3)`);
        }
        return result;
      }
      return find(1, "1");
    }
复制代码

growing functions

个人函数, 进化吧! 通常对函数的需求体如今两方面:

  1. 重复的代码段,很大程度上逻辑类似.若是不用函数的隐患有:
  2. 最重要的不是性能, 而是屡次重复的代码更大可能的出错.
  3. 其次是代码太多不利于人阅读.
  4. 再而后才会是机器性能的浪费.
  5. 你觉的那应该有这么一个函数,尽管还没写,预先的感受.

农场初版

  1. 007 Cows
  2. 011 Chickens

即保证数字为3, 真的是闻所未闻, 用字符串长度来作判断让其一次次的加0直到3位...

function printFarmInventory(cows, chickens) {
  let cowString = String(cows);
  while (cowString.length < 3) {
    cowString = "0" + cowString;
  }
  console.log(`${cowString} Cows`);
  let chickenString = String(chickens);
  while (chickenString.length < 3) {
    chickenString = "0" + chickenString;
  }
  console.log(`${chickenString} Chickens`);
}
printFarmInventory(7, 11);
复制代码

农场第二版

当咱们在农场又加了猪以后, 重复的代码块被封装再一个函数里面.

function printZeroPaddedWithLabel(number, label) {
  let numberString = String(number);
  while (numberString.length < 3) {
    numberString = "0" + numberString;
  }
  console.log(`${numberString} ${label}`);
}

function printFarmInventory(cows, chickens, pigs) {
  printZeroPaddedWithLabel(cows, "Cows");
  printZeroPaddedWithLabel(chickens, "Chickens");
  printZeroPaddedWithLabel(pigs, "Pigs");
}

printFarmInventory(7, 11, 3);
复制代码

函数名字又臭又长, 这里面有几个基本的信息

  • 数字补全到多少位(width), 须要补全(width-length)位, 可能农场变大以后就上千上万须要更多位
  • 数字自己是多少(number), 它的范围若是超过宽度呢呢?
  • 用什么补全会变化么? 不会..由于是用0, 这叫封装不变的, 留出变化的接口(函数的输入)

一个名字意思精炼小巧的函数, 不只能让人一眼看出它的意义, 还可让别的代码复用这种基础的函数, 想想语言自己带的那些经常使用的函数, 越是通用越是功能单一, 越是能够互相组合成为更强大的功能. 想想键盘上的26个字符, 不只能打出成千上万的英文文章, 还能打出汉字来.靠的就是抽象基本模式,使得记忆负担小, 这叫讨人喜欢, 而后互相组合能打出各类字, 这叫功能强大. 若是是无心义的编码,你要记2k多种没有规律的基本汉字的编码, 不讨人喜欢吧, 虽然功能也强大.

原则: 不要乱优化修改, 除非你明确的须要, 否则一切从简,快,正确. 当你仅仅须要一朵花的时候, 那就不要又设置花盆又设置肥料. 要控制住你的冲动, 你优化的东西可能和你的目的没多大关联, 仅仅是来自基因的倾向性要让你熟悉探索周围环境, 由于你可能没写多少有意义的代码, 却浪费时间改来改去让机器内存下降0.01%?那主要任务怎么办?

function zeroPad(number, width) {
  let string = String(number);
  while (string.length < width) {
    string = "0" + string;
  }
  return string;
}

function printFarmInventory(cows, chickens, pigs) {
  console.log(`${zeroPad(cows, 3)} Cows`);
  console.log(`${zeroPad(chickens, 3)} Chickens`);
  console.log(`${zeroPad(pigs, 3)} Pigs`);
}

printFarmInventory(7, 16, 3);
复制代码

functions and side effects

函数能够用来return一个有用的值, 也能够side effect.

但主要做用在return的值上面的函数, 才能够有用的和其余函数组合.为啥? 由于side effect就在函数内部, 输出的信息无法传给别的函数.

pure function: 不依赖其余代码的side effect, 也不输出本身的side effect. 每次一样的输入, 都会产生一样的输出. 一个纯函数若是在某一个地方正常, 那么它在全部地方都正常.而其余函数可能依赖的环境不一样.

在机器朝向纯数学发展的路上时, 纯函数确定拥有更严谨有效的解决问题的能力, 但... 机器并非纯粹的理想产物,它被现实一代代所创造优化, 因此用面向机器的代码在目前每每更高效, 而纯函数做为高级抽象, 耗费更多的资源.

Exercise

min()函数

let min = (x,y) => x>y?y:x;
复制代码

recusion isEven()函数

基本:

  1. 0是偶数
  2. 1是奇数 演绎: N的奇偶性和N-2是同样的 终止条件: n=0或n=1;
let isEven = (x) => {
  let result;
  switch(x) {
    case 0:
      result = false;
      break;
    case 1:
      result = true;
      break;
    default:
      result = isEven(x>0?x-2:x+2);
      break; 
  }
  return result;
};
console.log(isEven(50));
// → true
console.log(isEven(75));
// → false
console.log(isEven(-1));
// → ??
// 这个屡次判断正负的语句能够提早置于外部, 讲这个函数封闭为闭包访问静态变量, 使判断正负只运行一次.
复制代码

B counting

let countChar = (string, char) => {
      let cnt = 0;
      for(let i=0; i<string.length; i++) {
        if(string[i] === char)cnt++;
      }
      return cnt;
    }
    console.log(countChar("HelHsdfklkjlkhloHi", "H"))
复制代码
相关文章
相关标签/搜索