一、JS内功修炼
1、专业术语
1、创建阶段
创建阶段,指函数被调用但未执行任何代码时,此时一共拥有3个属性的对象:
executionContent = {
scopeChain:{},//创建作用域链(scope,chain)
variableObject:{},//初始化变量、函数、形参
this:{}//指定this
}
2、代码执行阶段
代码执行阶段主要工作是:
- 分配变量、函数引用、赋值
- 执行代码
3、执行上下文栈
注:JS开启多线程:new Worker(执行的文件地址),栈数据处理上是先进先出**
1、浏览器中的JS解析器是单线程的,相当于浏览器中同一个时间只能做一件事情
2、代码中只有有一个全局上下文,和无数个函数执行上下文,这些组成了执行上下文栈
3、一个函数的执行上下文,在函数执行完毕后,会被移除上下文栈
二、作用域
js中有全局作用域、函数作用域,es6中又增加了块级作用域。作用域的最大用途就是隔离变量或函数,并控制他们的是生命周期。作用域是在函数执行上下文创建时定义好的,不是函数执行时定义的
1、词法作用域&动态作用域
- 词法作用域:函数的作用域在函数定义的时候就决定了。(JavaScript采用的时静态作用域)
- 动态作用域:函数的作用域时在函数调用的时候才决定的
简单的例子来表述一下
var value = 1
function myFunc () {
console.log(value)
}
function bar () {
var value = 2
myFunc()
}
bar()
//bar函数执行-->myFunc函数执行-->查询value值(没有)-->向上查找(var value == 1)
静态作用域,只看定义时位置,就像你和别人是邻居,你和女朋友吵架,女朋友跑了出去,不在家。你只需要去外面找就行了,别人家就算有也是别人家的女朋友,但肯定不是你女朋友对不对????
2、函数作用域&块级作用域
- 函数作用域:已声明函数的形式,将内部代码“隐藏起来”,从而形成函数作用域
- 块级作用域:从ES3开始,
try/catch
结构在catch
分布中具有块级作用域。在ES6中引入了let/const
关键字(类似于var,但不相同),用来在任意代码块中声明变量。if(...){let a = 2}
会声明一个劫持了if
的{...}
块的变量,并且将变量添加到这个块中
```javascript // 块级作用域 for(var i = 0; i<10;i++) {// 函数作用域
function myFunc () {
var value = 1
console.log(value) // 1
}
console.log(value) //value is not defined
myFunc()
} console.log(i)//10
for(let i = 0; i<10;i++) {
} console.log(i)//i is not defined
<a name="cllRU"></a>
## 3、匿名函数表达式&&`IIFE`
- 匿名函数表达式:顾名思义,就是没有名字标识的**函数表达式(注意:函数声明则不可以省略函数名)**
- `IIFE`:最常见的用法其实就是使用了匿名函数表达式并最后加入`()`,让他立即执行。
```javascript
var a = 2
(function () {
var a = 3
console.log(a)//3
//匿名函数表达式内是块级作用域
})()
console.log(a)//2
他的作用主要是以下几点:
- 避免命名冲突
- 减少内存占用
- 产生块级作用域,造成作用域隔离,关于为啥要存在块级作用域,请参照《ECMAScript 6 入门》
三、作用域链
当一个块或者函数嵌套在另一个快或函数中时,就发生了作用域的嵌套。在当前函数中如果js引擎无法找到某个变量就会往上一级嵌套的作用域中去寻找,知道该变量或者抵达全局作用域,这样的链式关系就成为作用域链
1、什么是自由变量
首先认识一下什么是自由变量。如以下代码中,console.log(a)
要的得到a变量,但是在当前作用域中没有定义a(可以对比一下b)。当前作用域没有定义的变量,这称为自由变量。自由变量的值如何得到——向父级作用域寻找(注意:这种说法不严谨)
var a = 1
function myFunc() {
var b = 2
console.log(a) // 这里的a就是一个自由变量
console.log(b)
}
myFunc()
2、什么是作用域链
如果父级也没有呢?再一层一层想上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链。
var a = 1
function myFunc1() {
var b = 2
function myFunc2 () {
var c = 3
console.log(a)// 自由变量,顺作用域链向父作用域找
console.log(b) // 自由变量,顺作用域链向父作用域找
console.log(c)// 本作用的变量
}
myFunc2()
}
myFunc1()
3、关于自由变量的取值
关于自由变量的取值,上面提到要到父作用域中取,其实并不严谨,会产生歧义
var x = 10
function myFunc() {
console.log(x)
}
function show (f) {
(function(){
var x = 20
f() // 10 不是20
})()
}
show(myFunc)
在函数中,取自由变量的值时,要到那个作用域中取?——要到创建函数的那个作用域中去取,无论函数在哪调用,所以上面说法不严谨,用这句话描述会更加贴切:要到创建这个函数的那个域的作用域中取值,这里强调创建,而不是调用!!其实这就是所谓的静态作用域
四、闭包
高级程序设计三中:闭包是指有权访问另一个函数作用域中的变量的函数,可以理解为(能够读取其他函数内部变量的函数)
1、闭包的特性
函数嵌套函数
函数内部可以引用外部的参数和变量
参数和变量不会被垃圾回收机制回收
2、闭包的定义和优缺点
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量
闭包的缺点就是常驻内存,会增加内存使用量,使用不当很容易造成内存泄漏
闭包是JavaScript
语言的一大特点,主要应用闭包场合主要是为了:设计私有的方法和变量。一般函数执行完毕后,局部活动对象就会被销毁,内存中仅仅保存全局作用域,但闭包的情况不同!
3、嵌套函数的闭包
function myFunc () {
var a = 1;
retrun function () {
alert(a++)
}
}
var fun = myFunc()
fun()//1 执行后a++,然后a还在
fun()// 2
fun = null // a被回收
闭包会使变量始终保存在内存中,如果不当使用会增加内存消耗
JavaScript
的垃圾回收原理
- 在
JavaScript
中,如果一个对象不在被引用,那么在这个对象就会被GC回收 - 吐过两个对象互相引用,而不被第三者所引用,那么这两个互相引用的对象也会被回收
五、this
1、函数直接调用时
默认模式下this指向window,严格模式下this指向undefined
//默认模式
function myFunc () {
console.log(this)//this是window
}
var a = 1
myFunc()
//严格模式
"use strict"
function myFunc () {
console.log(this)//this是undefined
}
var a = 1
myFunc()
2、函数被别人调用时
this指向调用对象
function myFunc () {
conosle.log(this) //this是对象a
}
var a = {
myfunc : myFunc
}
a.func()
3、new一个实例时
function Persion (name) {
this.name = name
console.log(this)//this指实例p
}
var p = new Persion('yongzhi')
4、apply、call、bind时
function getColor (color) {
this.color = color
console.log(this)//这里指向window
}
function Car (name,color) {
this.name = name //this指向实例car
getColor.call(this,color)// 这里的this从window变成car
}
var car = new Car('别克','white')
5、箭头函数时
//setTimeout是全局函数,内部this指向window
var a = {
myfunc: function() {
setTimeout(function() {
console.log(this) // this是window
})
}
}
a.myfunc()
var a = {
myfunc: function() {
var that = this
setTimeout(function () {
console.log(that)//this指向a
})
}
}
a.myfunc()
var a = {
myfunc: function () {
setTimeout(()=>{
console.log(this)//this指向a
})
}
}
a.myfunc
总结
1、函数直接调用,不管函数被放在了什么地方,默认情况下this都是指向window(严格模式下this为undefined)
2、函数被调用,被那个对象调用,this就指向那个对象
3、在构造函数中,类中(函数体中)出现的this.xxx = xxx中this就是当前类的一个实例
4、call、apply时,this时第一个参数。bind要优先于call/apply
5、箭头函数没有实例,也就是没有自己的this,指向外层非箭头函数实例、嵌套的对象、window对象