一、JS内功修炼


1、专业术语

  1. 常量、变量、数据类型
  2. 形参、实参
  3. 匿名函数、具名函数、自执行函数
  4. 函数声明、函数表达式
  5. 堆、栈

    2、执行上下文

    当函数执行时,会创建一个称为执行上下文的环境,分为创建和执行两个阶段

1、创建阶段

创建阶段,指函数被调用但未执行任何代码时,此时一共拥有3个属性的对象:

  1. executionContent = {
  2. scopeChain:{},//创建作用域链(scope,chain)
  3. variableObject:{},//初始化变量、函数、形参
  4. this:{}//指定this
  5. }

2、代码执行阶段

代码执行阶段主要工作是:

  1. 分配变量、函数引用、赋值
  2. 执行代码

3、执行上下文栈


注:JS开启多线程:new Worker(执行的文件地址),栈数据处理上是先进先出**
1、浏览器中的JS解析器是单线程的,相当于浏览器中同一个时间只能做一件事情
2、代码中只有有一个全局上下文,和无数个函数执行上下文,这些组成了执行上下文栈
3、一个函数的执行上下文,在函数执行完毕后,会被移除上下文栈

二、作用域


js中有全局作用域、函数作用域,es6中又增加了块级作用域。作用域的最大用途就是隔离变量或函数,并控制他们的是生命周期。作用域是在函数执行上下文创建时定义好的,不是函数执行时定义的

1、词法作用域&动态作用域

  • 词法作用域:函数的作用域在函数定义的时候就决定了。(JavaScript采用的时静态作用域)
  • 动态作用域:函数的作用域时在函数调用的时候才决定的

简单的例子来表述一下

  1. var value = 1
  2. function myFunc () {
  3. console.log(value)
  4. }
  5. function bar () {
  6. var value = 2
  7. myFunc()
  8. }
  9. bar()
  10. //bar函数执行-->myFunc函数执行-->查询value值(没有)-->向上查找(var value == 1)

静态作用域,只看定义时位置,就像你和别人是邻居,你和女朋友吵架,女朋友跑了出去,不在家。你只需要去外面找就行了,别人家就算有也是别人家的女朋友,但肯定不是你女朋友对不对????

2、函数作用域&块级作用域

  • 函数作用域:已声明函数的形式,将内部代码“隐藏起来”,从而形成函数作用域
  • 块级作用域:从ES3开始,try/catch结构在catch分布中具有块级作用域。在ES6中引入了let/const关键字(类似于var,但不相同),用来在任意代码块中声明变量。if(...){let a = 2}会声明一个劫持了if{...}块的变量,并且将变量添加到这个块中
    1. // 函数作用域
    2. function myFunc () {
    3. var value = 1
    4. console.log(value) // 1
    5. }
    6. console.log(value) //value is not defined
    7. myFunc()
    ```javascript // 块级作用域 for(var i = 0; i<10;i++) {

} console.log(i)//10

for(let i = 0; i<10;i++) {

} console.log(i)//i is not defined

  1. <a name="cllRU"></a>
  2. ## 3、匿名函数表达式&&`IIFE`
  3. - 匿名函数表达式:顾名思义,就是没有名字标识的**函数表达式(注意:函数声明则不可以省略函数名)**
  4. - `IIFE`:最常见的用法其实就是使用了匿名函数表达式并最后加入`()`,让他立即执行。
  5. ```javascript
  6. var a = 2
  7. (function () {
  8. var a = 3
  9. console.log(a)//3
  10. //匿名函数表达式内是块级作用域
  11. })()
  12. console.log(a)//2

他的作用主要是以下几点:

  1. 避免命名冲突
  2. 减少内存占用
  3. 产生块级作用域,造成作用域隔离,关于为啥要存在块级作用域,请参照《ECMAScript 6 入门》

    三、作用域链


当一个块或者函数嵌套在另一个快或函数中时,就发生了作用域的嵌套。在当前函数中如果js引擎无法找到某个变量就会往上一级嵌套的作用域中去寻找,知道该变量或者抵达全局作用域,这样的链式关系就成为作用域链

1、什么是自由变量

首先认识一下什么是自由变量。如以下代码中,console.log(a)要的得到a变量,但是在当前作用域中没有定义a(可以对比一下b)。当前作用域没有定义的变量,这称为自由变量。自由变量的值如何得到——向父级作用域寻找(注意:这种说法不严谨)

  1. var a = 1
  2. function myFunc() {
  3. var b = 2
  4. console.log(a) // 这里的a就是一个自由变量
  5. console.log(b)
  6. }
  7. myFunc()

2、什么是作用域链

如果父级也没有呢?再一层一层想上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链。

  1. var a = 1
  2. function myFunc1() {
  3. var b = 2
  4. function myFunc2 () {
  5. var c = 3
  6. console.log(a)// 自由变量,顺作用域链向父作用域找
  7. console.log(b) // 自由变量,顺作用域链向父作用域找
  8. console.log(c)// 本作用的变量
  9. }
  10. myFunc2()
  11. }
  12. myFunc1()

3、关于自由变量的取值

关于自由变量的取值,上面提到要到父作用域中取,其实并不严谨,会产生歧义

  1. var x = 10
  2. function myFunc() {
  3. console.log(x)
  4. }
  5. function show (f) {
  6. (function(){
  7. var x = 20
  8. f() // 10 不是20
  9. })()
  10. }
  11. show(myFunc)

在函数中,取自由变量的值时,要到那个作用域中取?——要到创建函数的那个作用域中去取,无论函数在哪调用,所以上面说法不严谨,用这句话描述会更加贴切:要到创建这个函数的那个域的作用域中取值,这里强调创建,而不是调用!!其实这就是所谓的静态作用域

四、闭包


高级程序设计三中:闭包是指有权访问另一个函数作用域中的变量的函数,可以理解为(能够读取其他函数内部变量的函数)

1、闭包的特性

  1. 函数嵌套函数
  2. 函数内部可以引用外部的参数和变量
  3. 参数和变量不会被垃圾回收机制回收

    2、闭包的定义和优缺点

    闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量

闭包的缺点就是常驻内存,会增加内存使用量,使用不当很容易造成内存泄漏

闭包是JavaScript语言的一大特点,主要应用闭包场合主要是为了:设计私有的方法和变量。一般函数执行完毕后,局部活动对象就会被销毁,内存中仅仅保存全局作用域,但闭包的情况不同!

3、嵌套函数的闭包

  1. function myFunc () {
  2. var a = 1;
  3. retrun function () {
  4. alert(a++)
  5. }
  6. }
  7. var fun = myFunc()
  8. fun()//1 执行后a++,然后a还在
  9. fun()// 2
  10. fun = null // a被回收

闭包会使变量始终保存在内存中,如果不当使用会增加内存消耗

JavaScript的垃圾回收原理

  1. JavaScript中,如果一个对象不在被引用,那么在这个对象就会被GC回收
  2. 吐过两个对象互相引用,而不被第三者所引用,那么这两个互相引用的对象也会被回收

五、this


1、函数直接调用时

默认模式下this指向window,严格模式下this指向undefined

  1. //默认模式
  2. function myFunc () {
  3. console.log(this)//this是window
  4. }
  5. var a = 1
  6. myFunc()
  7. //严格模式
  8. "use strict"
  9. function myFunc () {
  10. console.log(this)//this是undefined
  11. }
  12. var a = 1
  13. myFunc()

2、函数被别人调用时

this指向调用对象

  1. function myFunc () {
  2. conosle.log(this) //this是对象a
  3. }
  4. var a = {
  5. myfunc : myFunc
  6. }
  7. a.func()

3、new一个实例时

  1. function Persion (name) {
  2. this.name = name
  3. console.log(this)//this指实例p
  4. }
  5. var p = new Persion('yongzhi')

4、apply、call、bind时

  1. function getColor (color) {
  2. this.color = color
  3. console.log(this)//这里指向window
  4. }
  5. function Car (name,color) {
  6. this.name = name //this指向实例car
  7. getColor.call(this,color)// 这里的this从window变成car
  8. }
  9. var car = new Car('别克','white')

5、箭头函数时

  1. //setTimeout是全局函数,内部this指向window
  2. var a = {
  3. myfunc: function() {
  4. setTimeout(function() {
  5. console.log(this) // this是window
  6. })
  7. }
  8. }
  9. a.myfunc()
  10. var a = {
  11. myfunc: function() {
  12. var that = this
  13. setTimeout(function () {
  14. console.log(that)//this指向a
  15. })
  16. }
  17. }
  18. a.myfunc()
  19. var a = {
  20. myfunc: function () {
  21. setTimeout(()=>{
  22. console.log(this)//this指向a
  23. })
  24. }
  25. }
  26. a.myfunc

总结

1、函数直接调用,不管函数被放在了什么地方,默认情况下this都是指向window(严格模式下this为undefined)
2、函数被调用,被那个对象调用,this就指向那个对象
3、在构造函数中,类中(函数体中)出现的this.xxx = xxx中this就是当前类的一个实例
4、call、apply时,this时第一个参数。bind要优先于call/apply
5、箭头函数没有实例,也就是没有自己的this,指向外层非箭头函数实例、嵌套的对象、window对象