热身
无处不在的JavaScript
- JavaScript和其他语言的差异
- 函数是一等公民。函数和其他对象共存。可以字面量创建函数、函数可以作为函数参数传递、函数可以赋值给变量、可以作为返回值从函数返回。
- 函数闭包。在一个函数中用到了外部变量,则这个函数为一个闭包。(描述准确吗?)
- 作用域,函数级别变量与全局变量:var。块级作用域:let、const。
- 基于原型的面向对象————区别于基于类的面向对象
- JavaScript特性
- 生成器
- promise
- 代理,控制对特定对象的访问
- 高级数组
- Map,字典集合。Set,不重复项目的集合
- 正则表达式
- 模块————项目管理 3、理解浏览器
- DOM
- 事件
- 浏览器API 4、小结
- 核心JavaScript机制:函数、函数闭包、原型。特性:生成器、Promise、代理、映射、集合(Map、Set)和模块。
运行时的页面构建过程
用户输入URL,浏览器将请求发送给服务器,服务器响应请求,浏览器接收请求并处理HTML、CSS和JavaScript并构建页面。页面构建完毕,浏览器进入事件处理直到用户关闭页面。
页面构建阶段
- 大致过程
- 解析HTML
- 构建DOM和执行JavaScript代码之间交替执行
- 执行JavaScript代码
- 所有包含在脚本元素中的JavaScript代码由浏览器JavaScript引擎执。FireFox的Spidermonkey引擎。Chrome和Opera的V8引擎。
- 浏览器JavaScript引擎的全局变量window和window重要属性document,document为当前页面的DOM。
- JavaScript脚本可以更改DOM,但是要在更改的DOM构建之后,所以一般Script标签都放在最后。
事件处理
1、检查事件队列,有则事件处理,无则继续检查事件队列。(事件都是异步的) 2、注册事件处理器
- on:缺点是一个事件只能有一个事件处理器,且可能会被覆盖。
- addEventListener:一个可以注册多个事件处理器
- on和addEventListener混用 可以混用
小结
- 浏览器接受的HTML代码用作创建DOM的蓝图,他是客户端Web应用结构的内部展示阶段。
- 我们使用JavaScript代码来动态地修改DOM以便给Web应用带来动态行为
- 客户端Web应用的执行分为两个阶段
- 页面构建代码用于创建DOM的。而全局JavaScript代码是遇到script节点时执行。在这个执行过程中,JavaScript代码能够以任意程度改变当前的DOM,还能注册事件处理器————事件处理器是一种函数,当某个特定事件发生后被执行。注册分为on与addEventListener
- 事件处理————同一时刻,只能处理多个事件中的一个,处理顺序是事件生成的顺序。事件处理阶段依赖事件队列,所有事件都以其出现的顺序存储在事件队列中。事件循环会检查事件队列的对头,如果检测到了一个事件,那么相应的事件处理器会被调用
3 初步函数
函数是第一类对象。函数与对象共存,函数也可以被视为其他人一类型的JavaScript对象。
3.1 函数式的不同点到底是什么
3.1.1 函数是第一类对象
- 函数可以通过字面量创建(表达式右边)
- 作为函数参数传递(回调函数)
- 作为函数返回值返回(闭包场景)
- 可以动态创建和分配属性(函数是第一类对象) 对象能做的任何一件事,函数也可以做。函数也是对象,唯一特殊之处在于它是可以调用的。
3.1.2 回调函数
建立的函数会被其他函数在稍后某个合适时间点再回来调用(回调是异步和同步之争)
3.2 函数作为对象的乐趣
函数能动态创建和添加属性带来的
3.2.1 存储函数:给函数动态添加属性,知道此函数是否被添加过。
3.2.2 自记忆函数:能够记住已计算的结果
优点:
- 函数调用会寻找之前调用所得到的值,有性能收益。 缺点:
- 任何类型的缓存必然为性能牺牲内存
- 纯粹主义者认为缓存逻辑不应该和业务逻辑混合
- 难做负载测试或估计算法复杂度
3.3 函数定义
- 函数声明和函数表达式
- 箭头函数
- 函数构造函数
- 生成器函数-ES6,能够让我们创建不同于普通函数的函数,在应用程序执行过程中,这种函数能够推出再重新进入,在这些再进入之间保留函数内变量的值。我们可以定义生成器版本的函数声明、函数表达式、函数构造函数。
3.3.1 函数声明和函数表达式
3.3.2 箭头函数
3.4 函数的实参和形参
剩余参数
function(first,...remainingNums)剩余参数只能放在函数最后一个参数
默认参数
function(a,b='b',c=a+''+b)
3.5 小结
- 把JavaScript看做函数式语言能书写复杂的代码
- 作为第一类对象,函数和JavaScript中其他对象一样,具有以下功能
- 通过字面量创建
- 作为函数参数传递
- 作为函数返回值返回
- 赋值给其他变量或方法
- 动态创建和分配属性
- 回调函数是被代码随后”回来调用“的函数
- 函数具有属性,而且这些属性能够存储任何信息,可以利用这个特性来做很多事情:
- 可以在一个函数中存储另一个函数用于之后的引用和调用
- 可以用函数属性创建一个缓存(记忆),用于减少不必要的计算
- 不同类型的函数:声明函数、函数表达式、箭头函数、生成器函数
- 函数声明和函数表达式是两种最主要的函数类型。函数声明必须具有函数名,在代码中它必须作为一个独立语句的存在。函数表达式可以不必有函数名,但它必须作为其他语句的一部分。
- 箭头函数是JavaScript的一个新增特性,这个特性让我们可以用更简洁的方式来定义函数
- 形参是函数定义时列出的变量,而实参是函数调用时传递给函数的值
- 函数的形参列表和实参列表长度可以不同
- 未赋值的形参为undefined
- 传入的额外实参不会被赋给任何一个命名形参
- 剩余参数和默认参数
4 函数进阶 理解函数的调用
4.1 使用隐式函数参数
4.1.1 arguments 参数
- 借此可以实现原生JavaScript并不能支持函数重载,并且可以实现函数参数数量可变的可变函数
- 避免把arguments参数当成数组。他有length属性,可以通过下标访问到每一个元素。但是它并非JavaScript数组,数组的方法用在其上可能会报错
arguments作为函数参数的别名
- 改变他既改变函数参数,改变函数参数既改变他
避免使用别名
- 在严格模式下,arguments不再是别名
4.1.2 this参数:函数上下文
- 与
函数定义方式、函数定义位置、函数调用环境有关
4.2 函数调用
- 直接调用
- 作为对象方法
- 作为构造函数
- apply/call
- 作为事件回掉函数,this一般会指向触发事件的函数(不算调用,算影响调用)
4.2.1 直接调用
- 严格模式下undefine
- 非严格模式下window
let a = function () {
return this
}
let b = function () {
let c = function(){
return this
}
return c()
}
console.log(a() === window)//true
console.log(b() === window)//true
4.2.2 作为方法调用
- 当函数作为某个对象的方法被调用时,该对象就会成为函数的上下文,并且在函数内部可以通过参数访问(this)。
- 方法内部可以通过this引用该方法的宿主对象。
4.2.3 作为构造函数被调用
-
关键词new
-
过程
- 创建一个新对象
- 该新对象作为this参数传递给构造函数,变成构造函数的上下文
- 新对象作为new运算符的返回值
-
关键点
- 创建并初始化先对象
- 新对象作为构造函数的返回值返回
-
当函数有自身返回值,并且new时
- 构造函数返回的是引用类型,则丢弃新创建的对象,返回构造函数对象
- 构造函数返回的是非引用类型,则返回new运算符的返回值
-
注意点
4.2.4 使用apply和call
- 案例
<button id="test">点击</button>
<script>
function Button(){
this.clicked=false
this.click = function(){
this.clicked=true
}
}
let button = new Button()
let elem = document.getElementById('test')
elem.addEventListener('click',button.click)
</script>
//点击之后,button.clicked=false elem.clicked=true
通常情况下,事件回调函数的上下文就是触发事件的对象在此例子中,浏览器的事件处理系统吧调用的上下文定义为事件触发的目标元素,因此上下文为元素,而非button对象
<button id="test">点击</button>
<script>
function Button(){
this.clicked=false
this.click = function(){
this.clicked=true
}
}
let button = new Button()
let elem = document.getElementById('test')
elem.addEventListener('click',button.click.bind(button))
</script>
//用call或者apply是相当于立即执行了函数
- apply和call的区别 apply参数为数组 call参数不是数组
4.3 解决函数上下文问题
- 箭头函数,与定义的上下文绑定
- apply,call执行绑定上下文
- bind
4.3.1 使用箭头函数绕过函数上下文
<button id="test">点击</button>
<script>
function Button(){
this.clicked=false
this.click = ()=>{
this.clicked=true
//这里的this是如果是由new之后的对象调用,this便会指向对象
}
}
let btn = {
clicked:false,
click : ()=>{
this.clicked=true
//这里的this是window
}
}
let button = new Button()
let elem = document.getElementById('test')
elem.addEventListener('click',button.click)
//这里的button.click是箭头函数,this是指向button
</script>
调用箭头函数,不会隐式传入this参数,而是从定义时的函数继承上下文。bind改变不了箭头函数
4.3.2 bind
- bind不会修改原始函数而是创建一个全新的函数
4.4 小结
- 当调用函数的时候,除了传入在函数定义中显示声明的参数以外,同时还传入两个隐式参数:arguments和this
- arguments参数是传入函数的所有参数的集合。(不是数组)具有length属性,表示传入的参数的个数,通过arguments参数还可获取那些与函数形参不匹配的参数。在非严格迷失下,arguments对象是函数参数的别名,修改arguments对象会修改函数的实参,可以通过严格模式避免修改函数的实参。
- this表示函数的上下文,即与函数调用相关联的对象。函数的定义方式和调用方式决定了this的取值。
- 函数的调用方式有4种
- 直接调用。一般this为window
- 作为方法调用。this为调用方法对象
- 作为构造函数。一般为new返回的对象
- apply、call 指定this
- 箭头函数没有单独的this值,this在箭头函数创建时确定(bind,apply,call不起作用的原因)
- 所有函数均可使用bind方法,创建新函数,并绑定到bind方法传入的参数上。被绑定的函数与原始函数具有一致的行为。