Javascript学习笔记
Javascript简介
Javascript的特性
与其他语言的不同之处:
- 在JavaScript 中,函数与其他对象共存,并且能够像任何其他对象一样地使用。函数可以通过字面量创建,可以赋值给变量,可以作为函数参数进行传递,甚至可以作为返回值从函数中返回。
- 函数闭包
- JavaScript 还没有(类似C语言中的)块级作用域下的变量,取而代之则只能依赖函数级别的变量和全局变量.
- 不同于其他主流的面向对象语言(例如C#、Java、Ruby)使用基于类的面向对象,JavaScript 使用基于原型的面向对象
特殊的功能, 特性:
- 生成器, 一种可以基于一次请求生成多次值的函数,在不同请求之间也能挂起执行.
- Promise,让我们更好地控制异步代码。
- 代理,让我们控制对特定对象的访问。
- 高级数组方法,书写更优雅的数组处理函数。
- Map,用于创建字典集合;Set,处理仅包含不重复项目的集合。
- 正则表达式,简化用代码书写起来很复杂的逻辑。
- 模块,把代码划分为较小的可以自包含的片段,使项目更易于管理。
转换编译器:
- 当新的标准制定, 新的特性出现时, 部分用户往往仍然使用老旧的浏览器, 一种解决方法是使用转换编译器, 将较新的Js代码转化为等价的, 能在当前浏览器运行的代码.
- Traceur 和 Babel是较为流行的两种
理解浏览器
浏览器环境概念:
- 文档对象模型(DOM): Web应用的结构化的UI表现形式, 最初由web应用的HTML代码构成;
- 事件: 大部分JavaScript 应用都是事件驱动的应用,这表示大部分代码执行在对某个特殊事件响应的上下文中。
- 如网络事件、计时器、用户生成事件例如点击、鼠标移动、键盘按压事件等。
- 浏览器 API: 获取设备的信息、存储本地数据或与远程浏览器交互的API。
调试工具: 探索 DOM、调试 JavaScript、编辑 CSS 样式和跟踪网络事件等。
测试: assert(condition, message); 第一个参数是一个应为真值的条件,第二个参数是当断言为假时所展示的一句话。
性能分析: 把要被测量的代码放在两个计时器调用之间;
1 | console.time("My operation"); //My operation是名字 |
跨平台开发: 通过使用浏览器和 Node.js(源自于浏览器的环境),你能够开发几乎你能想到的任何类型的应用。
- 桌面应用,通过使用如NW.js或Electron的库可以开发桌面应用。
- 包装javascript和浏览器核心
- 移动应用,使用类似Apache Cordova的框架开发。
- 使用Node.js 开发服务器端应用和嵌入式应用,Node.js 是源自于浏览器的环境,使用了很多类似浏览器的底层原理。
浏览器页面构造过程
主要由两个步骤构成: 页面构建和事件处理;
页面构建
又分为两个步骤, 在页面构建过程中交替进行;
- 解析HTML代码并构建文档对象模型(DOM);
- 执行 JavaScript 代码. 当遇到脚本节点时执行;
尽管 DOM 是根据 HTML 来创建的,两者紧密联系,但需要强调的是,它们两者并不相同。你可以把 HTML 代码看作浏览器页面 UI 构建初始DOM 的蓝图。
当页面构建遇到脚本元素时, 会暂停构建DOM转而执行JavaScript代码;
DOM与脚本的关系:
- window 对象是获取所有其他全局对象、全局变量(甚至包含用户定义对象)和浏览器 API 的访问途径。
- 全局 window 对象最重要的属性是 document,它代表了当前页面的 DOM。
- 通过使用这个对象,JavaScript 代码就能在任何程度上改变 DOM
全局代码与函数代码:
- 函数代码指的是包含在函数中的代码,全局代码指的是位于函数之外的代码;
- 全局代码以一种直接的方式自动执行,每当遇到这样的代码就一行接一行地执行。
- 函数代码必须被调用才执行;
事件处理
浏览器处理代码特性:
- 浏览器同一时刻只能执行一个代码片段,即所谓的单线程执行模型。
- 所有生成的事件都被放在同一个事件队列中(注册事件监听器), 从头部开始被处理;
事件类型:
- 浏览器事件,例如当页面加载完成后或无法加载时;
- 网络事件,例如来自服务器的响应(Ajax 事件和服务器端事件);
- 用户事件,例如鼠标单击、鼠标移动和键盘事件;
- 计时器事件,当timeout 时间到期或又触发了一次时间间隔。
注册事件监听器方式:
- 通过把函数赋给某个特殊属性;
window.onload = function(){};
, 将函数赋值给window
对象的onload
属性;- 这一方式的缺陷在于对于一个事件只能注册一个事件处理器, 创建新的处理器时会将上一个给改写掉;
- 通过使用内置addEventListener方法。
1 | document.body.addEventListener("mousemove", function() { //#为mousemove事件注册处理器 |
函数
函数与对象
函数中最重要的概念: 函数是第一类对(first-class objects),可以被视为其他任意类型的 JavaScript 对象。
- 能被变量引用:
- 能以字面量形式声明:
function ninjaFunction(){}
var ninja = {};
- 甚至能被作为函数参数进行传递。
call(function(){})
回调函数(callback): 将在随后调用的函数, 也即作为参数被其它函数执行的函数;
回调函数排序:我们提供一个函数用于比较, 返回值大于0需要调换,小于等于0不需要;在比较时调用回调来决定数组的顺序;
1 | var values = [0,1,2,9,6,5,3,4] |
储存函数: 存储元素唯一的函数集合;
1 | var store = { |
自记忆函数: 当函数计算得到结果时就将该结果按照参数存储起来,如果另外一个调用也使用相同的参数,我们则可以直接返回上次存储的结果;
1 | function isPrime(value) { |
函数定义方式
- 函数声明:
function myFun() { return 1;}
- 函数声明:作为独立表达式;函数表达式:作为其他语句的部分,作为右值/参数/返回值;
- 对于表达式, 函数名不是必须的,对于声明, 他们被引用的唯一方式是通过名字;
- 立即调用函数表达式IIFE):
(function(){})(2)
,创建了一个新函数并调用;- 括号的作用: 不加括号时, 以function开头的语句会被解释为声明, 然而没有函数名, 故而会报错
- 上图四个语句都是立即函数,但是使用一元操作符指明处理的是表达式,而非语句;符号得到结果没有被储存,关键在于IIFE被调用了;
- 箭头函数(lambda函数):
param => expression
- 省去function,大括号,return;
- param: 参数, 单个参数省略括号, 多个参数与声明一致;
- expression: 多行表达式需要
{}
;
- 函数构造函数: 以字符串形式构造函数;
new Function('a', 'b', 'return a + b')
- 生成器函数:在执行过程中,能够退出这个函数再重新进入,过程中保留函数内变量值;
function* myGen(){ yield 1; }
函数参数
- 参数性质:
- 实参多于形参: 按照顺序赋值, 多余的实参不会被赋值;
- 形参多于实参: 没有对应实参的形参则会被设为undefined;
- 剩余参数:
- 剩余参数以……做前缀, 且只能是最后一个参数, 被放到以去除…后的名称(reaminingNumbers)的数组中;
- 默认参数:
- 另一种方法是函数重载: 定义一个名字相同但参数不同的函数;但JavaScript不支持;
函数调用
- arguments参数: 传递给函数的所有参数的集合;
- 为实参的集合, 不论是否有对应形参;
- 通过数组下标方式访问参数,
arguments[i]
; - arguments.length获取实参个数,但它不是数组,只是与数组类似,在其上使用数组的方法会报错;
- 相比较之下, 剩余参数则是作为数组;
- arguments对象是函数参数的别名, 在函数内改变arguments对象的值也会改变对应形参,反之亦然;
- 在JavaScript 提供的严格模式(strict mode)中无法再使用别名。
"use strict";
- 在JavaScript 提供的严格模式(strict mode)中无法再使用别名。
- this参数:函数调用相关联的对象(函数上下文)
- this 参数的指向不仅是由定义函数的方式和位置决定的,同时还严重受到函数调用方式的影响;
- 函数调用的四种方式:
- 作为一个函数(function)直接被调用;
test()
- 非严格模式下,this==window全局对象;严格模式下this==undefined;
- 作为一个方法(method),关联在一个对象上,实现面向对象编程;
myobj.test()
, 此时this指向该对象; - 作为一个构造函数(constructor),实例化一个新的对象;
new ObjName()
- 当使用关键字 new 调用函数时,会创建一个空的对象实例并将其设置构造函数的上下文
- 当构造函数有非对象返回值时,用new调用则返回新建对象,直接调用则返回该值;
- 但当返回对象时, this对象将被舍弃;
- 构造函数命名通常以大写字母开头,为描述对象的名词;函数方法则以小写字母开头,为描述行为的动词;
- 通过函数的apply 或者call 方法;
obj.apply(...)
orobj.call(...)
- Button函数中, 原本通过button.click调用this应该指向button,但是由于我们将其绑定到了按钮上,故而this指向了elem元素;
- apply方法, 上下文对象和参数数组;
- call方法, 上下文对象和参数, 无需使用数组传递参数;
- forEach方法(call|apply)迭代数组
- 解决上下文问题的其他方法:
- 箭头函数:
- 箭头函数从定义时的所在函数继承上下文,相比较函数表达式指向全局对象;
- 与清单4.10比较:4.10中Button构造函数的click函数上下文被addEventListener绑定到了elem元素, 而箭头函数的click函数从Button函数处继承上下文,故仍然指向button.
- 存在的问题:
- click 箭头函数是作为对象字面量的属性定义的,对象字面量在全局代码中定义, 所以箭头函数this指向window;
- bind方法:
- 不管如何调用该函数,this 均被设置为对象本身。
- 被绑定的函数与原始函数行为一致,函数体一致。
- 箭头函数:
- 作为一个函数(function)直接被调用;
闭包与作用域
- 闭包能够允许函数访问并操作函数外部变量;
- 全局作用域实质上是一种闭包, 但是从未消失;
-
- 通过outerFunction我们封装了一个innerFunction,并将该function赋给全局变量later,从而能够访问到inner*,并且该函数的作用域为全局+outerFunction;
- 这里ninja是outerfunction的局部变量,按理来说应当无法访问,但是声明inner*时,创建了一个闭包,不仅包含了函数的声明,还包含了在函数声明时该作用域中的所有变量。
- 也即是说,创建闭包不仅保存了函数,还有其作用域内的变量;
- feints变量不是通过this.feints方式定义的,故而不能直接访问,但是其包含于Ninja的作用域中,能被this.feint()函数所访问;
- 这一功能有点类似于shiny的模块化,闭包被不同的参数调用,其内部变量互不影响;
全局执行上下文只有一个,当JavaScript程序开始执行时就已经创建了全局上下文;而函数执行上下文是在每次调用函数时,就会创建一个新的。
- 函数上下文是内部的, 而执行上下文是JS引擎追踪函数执行使用的;
- 基本和其他语言差异不大;
- 词法环境: 也即作用域(scopes);
- 当使用变量时, 从内向外开始查找, 从调用栈从上往下一级一级查, 直至找到或者是全局作用域中都没有而报错;
- 不是从定义函数的环境查找;
- 当使用变量时, 从内向外开始查找, 从调用栈从上往下一级一级查, 直至找到或者是全局作用域中都没有而报错;
- 变量类型:
- const关键字: 声明的变量的值无法变更(指用新的值覆盖);compared with var and let(能多次覆盖)
- 用于定义无需重新赋值的变量, 或者是某个固定的值(通常用于描述性变量名替代单纯数值);
- 不允许将全新的值赋值给const变量,但是可以修改;
- var关键字:声明变量是在距离最近的函数内部或是在全局词法环境中定义的;
- 与C不同,js不关注块级作用域,var声明的变量在距离最近的函数或全局作用域中实现;
- forLoop的块级作用域中声明的元素仍能被外部访问;
- 与var不同, let和const在最近词法环境定义变量(块级,循环,函数,全局);
- 块级作用域: for(){}, if(){}, with(obj){}, try{}/catch{},even simple {};
- 标识符注册:
- 定义在使用之后(谈恋爱要在世界拯救之后??);
- 注册流程:
- 找到函数声明, 创建arguments和函数参数;
- 扫描当前代码进行函数声明(不会扫描其他函数的函数体),对于所找到的函数声明,将创建函数,并绑定到当前环境与函数名相同的标识符上.
- 扫描当前代码进行变量声明。
- 在函数或全局环境中,查找所有当前函数以及其他函数之外通过 var 声明的变量,并查找所有通过 let 或 const 定义的变量。
- 在块级环境中,仅查找当前块中通过 let 或 const 定义的变量。
- const关键字: 声明的变量的值无法变更(指用新的值覆盖);compared with var and let(能多次覆盖)
generator和promise
生成器函数
- 基于每次请求生成值, 从而生成一组序列;
- 每次请求生成新的值 / 或者告诉我们不再生成新值;
- 在关键字function前加星号*,从而在生成器内部使用yield生成独立值;
- for-of循环: 新的循环方式;
- 将值赋给const变量得到迭代器object;
- iter.next()返回一个对象,包含
- result.value: 返回的值,如生成已结束则为undefined;
- result.done: 是否生成器结束,如已结束则为true;
- 调用next方法 -> 执行代码直到遇到yield -> 返回中间值;
yield* otherGenerator
将执行权交给另外的生成器;- 整体执行逻辑不变,仍旧是遇到yield就返回值, 相当于生成了一个栈, 新加一个生成器就加一层栈;
- 生成器用法:
- 生成ID序列:定义一个无限循环的生成器,每次返回++ID;
- 遍历DOM树:
- 深度优先,优先往下访问;
- 生成ID序列:定义一个无限循环的生成器,每次返回++ID;
- 向生成器发送值:
- 情况1:在初始状态调用并传入参数;
- 情况2:next方法传入参数;
- 用throw方法向迭代函数抛出一个错误,该错误会被catch()函数获取,传递给参数e;
- 生成器执行流程:
- 生成器比较特殊,它不会执行任何函数代码。而是生成一个新的迭代器再从中返回,通过在 代码中用 ninjaIterator 可以来引用这个迭代器。
- 由于迭代器是用来控制生成器的执行的,故而迭代器中保存着一个在它创建位置处的执行上下文。
- 每次调用next方法,不是像普通函数那样,生成新的上下文,而是把原有的上下文重新放入栈中;
- 挂起开始 — 创建了一个生成器后,它最先以这种状态开始。其中的任何代码都未执行。
- 执行 — 生成器中的代码已执行。执行要么是刚开始,要么是从上次挂起的时候继续的。
- 当生成器对应的迭代器调用了next方法,并且当前存在可执行的代码时,生成器都会转移到这个状态。
- 挂起让渡 — 当生成器在执行过程中遇到了一个yield表达式,它会创建一个包含着返回值的新对象,随后再挂起执行。生成器在这个状态暂停并等待继续执行。
- 完成 — 在生成器执行期间,如果代码执行到return语句或者全部代码执行完毕,生成器就进入该状态。
promise
- promise实例化对象传入的是两个函数参数,第一个为resolve函数表成功,reject表失败;
- 当承诺成功兑现(在promise上调用了resolve),前一个回调就会被调用,而当出现错误就会调用后一个回调函数(可以是发生了一个未处理的异常,也可以是在promise上调用了reject)
- promise 对象是对我们现在尚未得到但将来会得到值的占位符;
- 它是对我们最终能够得知异步计算结果的一种保证。如果我们兑现了我们的承诺,那结果会得到一个值。如果发生了问题,结果则是一个错误,一个为什么不能交付的借口。
回调函数缺陷:
- 错误难以处理:不是很理解这段话,学完再看;
- 执行连续步骤麻烦:
- 一个长期任务结束后我们可能会用得到的数据开启另一项任务,就需要不停的缩进+嵌套;
- 并列步骤需要书写多段类似代码:
- 错误难以处理:不是很理解这段话,学完再看;
promise执行逻辑:
- promise对象从pending开始,标记为未完成;
- 若promise对象resolve方法被调用,获取值,进入完成状态;
- 若reject方法被调用,则获取出错原因,进入完成状态;
- 需要注意的是第二个Inmmediatepromise,为什么会在”at code end”后执行?
- 拒绝promise:
- then中调用第二个回调函数
- then中只传入第一个回调函数, 错误通过catch获取
- then可以有很多步,从而完成任务流,而catch函数,只要前面有任何一个promise出错,就会将其捕捉;
- 不是主动调用,而是函数内部出错;
- Promise.all函数接受的是一个promise对象的数组;
- 只有全部成功才会被解决, 只要有一个失败就被拒绝;
- 拒绝与接受取决于第一个成功的promise;
生成器与promise结合
- 每个promise也即异步任务都被yield返回;
- 如果生成器的结果是一个被成功兑现的承诺,我们就是用迭代器的 next 方法把承诺的值返回给生成器并恢复执行
iteratorValue.then(res => handle(iterator.next(res)))
; - 如果出现错误,承诺被违背,我们就使用迭代器的throw方法抛出一个异常
.catch(err => iterator.throw(err))