刷视频的方式学的就只是API层面(如果是买网课的那种视频,当我没说),趁着周末这两天,趁着还有图书馆资源,去借了3本回来,分别是《CSS权威指南-上》,《CSS揭秘》,《你不知道的JavaScript-上卷》。这篇主要用来记录JavaScript的学习笔记,方便以后快速复习,也帮助现在加深印象。
一.作用域
学习之前要先有个常识问题,var a = 1 是两句声明,一个由编译器在编译时处理,另一个则由引擎在运行时处理。
步骤:
遇到var a ,编译器会先询问作用域中是否已经存在a。Y–》忽略改声明 继续执行;N–》要求作用域在当前作用域的集合中声明一个新的变量,并命名为a。
接下来编译器会为引擎生成运行时所需的代码,用来处理a = 1这个赋值,引擎会先询问作用域,在当前作用域集合中是否存在一个a。Y–》使用a;N–》继续查找
查找的方式:目的是对变量赋值(LHS),目的是获取变量的值(RHS)
ES5引入了🔒“严格模式”(禁止自动或隐式的创建全局变量)。
LHS查询失败–》并不会创建并返回一个全局变量,引擎会抛出同RHS查询失败类似的ReferenceError异常
RHS查询到了,但是对其进行了不合理操作—》抛出TypeError
作用域完全由函数声明的位置来定义,怎么才能使它变为在运行时呢? eval(),with 都不推荐去使用
词法作用域是在定义的时候确定,动态作用域是在运行的时候确定(this也是)
一道笔试题:
1 | function foo() { |
JavaScript中的作用域分为3个,全局作用域,函数作用域,块级作用域
1.函数作用域
每声明一个函数都会为其自身创建一个气泡,而其他结构都不会创建作用域气泡。
问题:函数名会污染所在作用域,但是不用函数名的话 又无法调用其执行
解决:
- 函数表达式:
1 | //IIFE |
- 匿名函数
匿名函数
问题:
1.匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难
2.如果没有函数名,当函数需要引用自身时只能使用已经废弃的arguments.callee引用
比如:在递归中,另一个函数需要引用自身 是在事件触发后事件监听器需要解绑自身
3.匿名函数省略了对于代码可读性/可理解性很重要的函数名。
解决:行内函数表达式
2.块作用域
比如for(){},with(用with从对象中创建出来的作用域仅在with声明中有效),try/catch,let,const
注意:当同一个作用域中的两个或多个catch分句用同一个标识符去声明错误变量时,很多静态检查工具还是会发出警告。实际上,这并不是重复定义,因为所有变量都被安全地限制在块作用域内部。
代码规范:为变量显示声明块作用域,并对变量进行本地绑定 。用{}括起来
二.提升
当既有函数又有变量的时候,会先进行函数提升,其次才是变量
三.闭包
一个JavaScript中近乎神话的概念—闭包
JavaScript中闭包无处不在,你只需要能够识别并拥抱它
1 | function wait(message) { |
将一个内部函数timer传递给setTimeout(..)。timer具有涵盖wait(..)作用域的闭包,因此还保有对变量message的引用
wait执行1000毫秒后,它的内部作用域并不会消失,timer函数依然保有wait作用域的闭包
:zap: 升级版理解:在引擎内部,内置的工具函数setTimeout(..)持有对一个参数的引用,这个参数也许叫fn或func等等。在这个例子中是timer,引擎会调用这个函数也就是timer,而词法作用域在这个过程中保持完整。
在定时器,事件监听器,Ajax请求,跨窗口通信,Web Workers或者任何其他的异步(或者同步)任务中,只要用了回调函数,实际上就是在用闭包
:baby: 每过1s打印出1 2 3 4 的方法:
方式一:var + IIFE
1
2
3
4
5
6
7for(var i = 1; i<5; i++){
(function(j){
setTimeOut(function timer(){
console.log(j)
},j*1000)
})(i)
}
方式二:let + 闭包
1
2
3
4
5for(let i = 1; i<5; i++){
setTimeOut(function timer(){
console.log(i)
},i*1000)
}
模块
:zap: 闭包最强大的一个应用:模块
模块模式需要具备2个必要条件:
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
1 | var foo = (function CoolModule() { |
四.this
学习之前必须要清楚this的两大误解:
- 指向自身?
- 指向函数的作用域?
指向自身吗?확실해요? :smile:我们来看看 一道笔试题:
1 | function foo(num) { |
执行foo.count = 0时,的确向函数对象foo添加了一个属性count。但是函数内部代码中的this.count中的this并不是指向那个函数对象foo。
如果想要得到4的输出,将this.count++ —–》 foo.count++
指向函数的作用域?
需要明确的是 :closed_lock_with_key: this在任何情况下都不指向函数的词法作用域。在JavaScript内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过JavaScript代码访问,它存在于JavaScript引擎内部。
:smile:同样来看看一道笔试题:
1 | function foo() { |
试图使用this联通foo()和bar()的词法作用域,从而让bar()可以访问foo()作用域里的a。:x: 不可能实现,使用this不可能在词法作用域中查到什么
:zap: 当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈),函数的调用方式,传入参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用。
虽然this的绑定规则完全取决于调用位置,但是只有foo()运行在非static mode下时,默认绑定才能绑定到全局对象;在严格模式下调用foo()则不影响默认绑定。
判断this:
①是否通过new调用?是—》this绑定的是新创建的对象
②是否通过call,apply调用?是—》this绑定的是指定对象
③是否通过某个上下文对象中调用?是—》this绑定的是上下文对象
④以上都不是的话,使用默认绑定—》严格模式下,this绑定到undefined;否则绑定到全局对象
DMZ对象:是一个空的非委托对象。
一种更安全的做法是传入一个特殊对象,把this绑定到这个对象不会对你的程序产生任何副作用,就像网络(以及军队)一样,我们可以创建一个DMZ(非军事区)对象
创建空对象的方法:Object.create(null)
- 除了有内置的bind()还有softBind()
1 | if(!Function.prototype.softBind) { |