你知道的JS-作用域,闭包,this,原型对象

​ 刷视频的方式学的就只是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
2
3
4
5
6
7
8
9
10
11
function foo() {
console.log(a) //3 不是2
}

function bar() {
var a = 3
foo()
}

var a = 2
bar()

JavaScript中的作用域分为3个,全局作用域,函数作用域,块级作用域

1.函数作用域

每声明一个函数都会为其自身创建一个气泡,而其他结构都不会创建作用域气泡。

问题:函数名会污染所在作用域,但是不用函数名的话 又无法调用其执行

解决:

  • 函数表达式:
1
2
3
4
5
6
7
//IIFE
(function foo(){
//code
...
})()
//意味着foo只能在...所代表的位置中被访问
//也就是说,foo变量名被隐藏在自身中
  • 匿名函数

匿名函数

问题:

​ 1.匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难

​ 2.如果没有函数名,当函数需要引用自身时只能使用已经废弃的arguments.callee引用

​ 比如:在递归中,另一个函数需要引用自身 是在事件触发后事件监听器需要解绑自身

​ 3.匿名函数省略了对于代码可读性/可理解性很重要的函数名。

解决:行内函数表达式

2.块作用域

比如for(){},with(用with从对象中创建出来的作用域仅在with声明中有效),try/catch,let,const

注意:当同一个作用域中的两个或多个catch分句用同一个标识符去声明错误变量时,很多静态检查工具还是会发出警告。实际上,这并不是重复定义,因为所有变量都被安全地限制在块作用域内部。

代码规范:为变量显示声明块作用域,并对变量进行本地绑定 。用{}括起来

二.提升

当既有函数又有变量的时候,会先进行函数提升,其次才是变量

三.闭包

一个JavaScript中近乎神话的概念—闭包

JavaScript中闭包无处不在,你只需要能够识别并拥抱它

1
2
3
4
5
6
function wait(message) {
setTimeout(function timer() {
console.log(message)
},1000)
}
wait("hello")

将一个内部函数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
    7
    for(var i = 1; i<5; i++){
    (function(j){
    setTimeOut(function timer(){
    console.log(j)
    },j*1000)
    })(i)
    }
  • 方式二:let + 闭包

    1
    2
    3
    4
    5
    for(let i = 1; i<5; i++){
    setTimeOut(function timer(){
    console.log(i)
    },i*1000)
    }

模块

:zap: 闭包最强大的一个应用:模块

模块模式需要具备2个必要条件:

1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)

2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var foo = (function CoolModule() {
var something = "cool"
var another = [1,2]

function doSomething() {
console.log(something)
}

function doAnother() {
console.log(another.join(" sleep "))
}

return {
doSomething: doSomething,
doAnother: doAnother
}
})()

foo.doSomething() //cool
foo.doAnother() //1 sleep 2 sleep

四.this

学习之前必须要清楚this的两大误解:

  • 指向自身?
  • 指向函数的作用域?

指向自身吗?확실해요? :smile:我们来看看 一道笔试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo(num) {
console.log("foo:" + num)
//记录foo被调用的次数
this.count++
}
foo.count = 0
var i
for(i = 0; i<10; i++){
if(i>5){
foo(i)
}
}
//foo: 6 foo: 7 foo: 8 foo: 9
console.log(foo.count) //0 why? 뭐예요?

执行foo.count = 0时,的确向函数对象foo添加了一个属性count。但是函数内部代码中的this.count中的this并不是指向那个函数对象foo。

如果想要得到4的输出,将this.count++ —–》 foo.count++

指向函数的作用域?

需要明确的是 :closed_lock_with_key: ​this在任何情况下都不指向函数的词法作用域。在JavaScript内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过JavaScript代码访问,它存在于JavaScript引擎内部。

:smile:同样来看看一道笔试题:

1
2
3
4
5
6
7
8
9
10
function foo() {
var a = 2
this.bar()
}

function bar() {
console.log(this.a)
}

foo() //RefereceError: a is not defined

试图使用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
//捕获所有的curried参数
var curried = [].slice.call(arguments,1)
var bound = function() {
return fn.apply(
(!this || this === (window || global))?obj:this,
curried.concat.apply(curried,arguments)
)
}
bound.prototype = Object.create(fn.prototype)
return bound
}
}