Day18:预编译-作用域链-本地存储

29 阅读11分钟

预编译,作用域链,本地存储.png

1.全局对象GO

全局环境:浏览器中,所有的script标签都在一个环境中,这个环境就叫做全局环境

全局对象(Global Object):浏览器环境中,js引擎会整合所有的script标签内容,产生一个全局对象,这个对象就叫做window对象

全局变量:在全局环境下定义的变量,script标签下定义的变量

全局函数:在script标签下定义的函数

特点

通过打印window可以看出,全局变量是作为window的一个属性存在的

2.活动对象AO

活动对象(Active Object):在函数被调用的时候产生的一个对象,用来保存当前函数内部的执行环境

局部变量:在函数内部定义的变量

局部函数:在函数内部定义的函数

local(本地)就是局部环境,走过a之后,对a进行了赋值

函数调用完之后就立即销毁,只能通过断点调试的方式来查看局部环境。

需要进入函数内部查看('本地'就代表局部环境

f2其实是局部对象AO的一个方法

关系

局部变量其实是作为活动对象(AO对象)的一个属性存在的

局部函数其实是作为活动对象(AO对象)的一个方法存在的

3.全局预编译

概念

在全局环境(script标签)下JS引擎的处理方式

流程

  1. 查找全局变量的声明,作为GO对象的属性名,值是undefined
  2. 再查找全局函数的声明,作为GO对象的属性名,值是function(代表这个函数)
  3. 从上往下依次执行代码(声明的语句会被略过)

预编译

console.log(a) // 函数a
var a = 10
console.log(a) // 10
function a(){
  console.log(a)
}
a() // 报错

产生的原因:代码有歧义,定义的变量名a和函数名a冲突了。导致程序报错。

想要了解报错的本质,需要知道js预编译的过程

js预编译的目的:消除歧义,让代码能够从上到下依次执行。(也称之为预处理)

具体流程

  1. 先生成GO对象(window对象)
  2. 再进行全局预编译
    1. 先查找全局变量的声明,作为GO对象的属性名

GO : {a : undefinde}

    1. 再查找全局函数的声明,作为GO对象的属性名,值是function(代表这个函数)

GO : {a : function}

    1. 从上往下依次执行代码(声明的语句会被略过)

执行第1行:打印a,打印function

执行第2行:给a赋值 GO : {a : 10}

执行第3行:打印a,打印的值是10

执行第7行:调用a,a现在是一个变量10,不能调用方法,报错

结论:在全局环境中,如果函数和变量同名,函数的优先级高,会被优先调用。

4.函数预编译

概念

在局部环境(函数里面)下JS引擎的处理方式

流程

  1. 在函数被调用的那一刻,就会为当前的函数生成一个AO对象。
  2. 查找局部变量的声明,作为AO对象的属性名,值是undefined
  3. 使用实参的值,替换形参的值
  4. 再查找局部函数的声明,作为AO对象的属性名,值是function(代表这个函数)
  5. 从上往下依次执行代码(声明的语句会被略过)
<script>
    function a(n){
        console.log(m)
        var m = 10 // 局部变量
        console.log(b)
        function b(){  // 局部函数
            console.log("bbb")
        }
        b()
    }
    a(100)
</script>

具体流程

  1. 生成GO对象
  2. 全局预编译
    1. 查询全局变量 没有
    2. 查询全局函数 有 GO{a ; function}
  1. 执行script里面的代码:声明的语句会被略过,直接执行11/24行,调用a函数。此时就会进行函数预编译
    1. 在函数被调用的那一刻,就会为当前的函数生成一个AO对象。
    2. 查找局部变量的声明,作为AO对象的属性名,值是undefined

AO{ m : undefined, n : undefined}

    1. 使用实参的值,替换形参的值

AO{ m : undefined, n : 100}

    1. 再查找局部函数的声明,作为AO对象的属性名,值是function(第2/10行)

AO{ m : undefined, n : 100 , b : function}

    1. 从上往下依次执行代码(声明的语句会被略过)

执行第3/11行,打印m,打印的是undefined

执行第4/13行,给m赋值 AO{ m : 10, n : 100 , b : function}

执行第5/15行,打印b 打印的是function

执行第9/21行,调用b 打印bbb

结论:在局部环境中,如果函数和变量同名,函数的优先级高,会被优先调用。

5.作用域

概念

就是一个区域,用来限定变量在某个范围内起作用

优点

提高程序可靠性,减少命令冲突(ES6语法规定,在同一个域中,不能定义相同的变量,否则会报错)

分类

全局作用域

在全局环境下

由script标签产生的区域,从计算机角度可以理解为window对象(GO对象)管控的区域

全局变量和全局函数在全局作用域下,在页面关闭的时候被销毁

局部作用域

在函数里

由函数产生的区域,从计算机角度,可以理解为当前函数对象(AO对象)管控的区域

局部变量和局部函数在局部作用域下,在函数执行完之后被销毁

块级作用域

在{}中(ES6)

if(true){
  var num1 = 20 // ES5语法,没有块级作用域概念,num1不受{}}影响
  let num2 = 30 // ES6语法,有块级作用域影响,num2出了{}}就无法使用
}

函数形参的本质

本质是函数内部的一个局部变量。

let定义的变量在同一个域中不能重复

ES6语法,存在于if语句,for循环等{}中,{}包含的区域就是块级作用域,

6.作用域链

概念

只要是代码,都会在一个作用域中,写在函数内部的就在局部作用域;没写在函数内部的,就写在全局作用域。

如果这个作用域内部还有函数,那么在这个作用域内,又可以诞生一个新的作用域,就形成作用域链

function f1(){
  var num1 = 10
  function f2(){
    console.log(num1)
  }
  f2()
}
f1()
console.dir(f1)

f1的作用域链

[[Scopes]]: Scopes[1]

0: Global {window: Window, self: Window, document: document, name: '', location: Location, …}

f2的作用域链

[[Scopes]]: Scopes[2]

0: Closure (f1) {num1: 10}

1: Global {window: Window, self: Window, document: document, name: '', location: Location, …}

作用

提供了一个查找机制,内部函数可以访问外部函数中的变量,使用链式查找来决定哪些数据能被内部函数访问。

本质

就是一个数据结构,函数内部是可以嵌套函数的,每一次嵌套都会形成新的作用域,把这些作用域串起来,就形成了作用域链。

具体流程

  1. JS引擎加载html页面,产生GO对象,在全局作用域中

GO{a :function}

  1. 全局预编译完成后,调用a函数,在调用那一刻,会为a函数产生一个a函数的AO对象,在局部作用域中。

现在a函数内部有两个作用域,一个是GO所在的全局作用域,一个是a函数所在的局部作用域AO

[scopes] ---- aAO { b : function }

---- GO { a : function }

  1. a函数预编译完成后,会调用b函数,在调用那一刻,会为b函数产生一个b函数的AO对象,在局部作用域中

现在b函数内部有三个作用域,GO(1),a函数(0),b函数(本身)

[scopes] ---- bAO { c : function }

---- aAO { b : function }

---- GO { a : function }

  1. b函数预编译完成之后,会调用c函数,在调用那一刻,会为c函数产生一个c函数的AO对象,在局部作用域中

现在b函数内部有四个作用域,GO(2),a函数(1),b函数(0),c函数(本身)

[scopes] ---- cAO { }

---- bAO { c : function }

---- aAO { b : function }

---- GO { a : function }

function a(){
  var num = 10
  function b(){
    console.log(num)
  }
  b()
}
a()

通过作用域链的角度分析,为什么打印的是10

分析程序的执行流程

  1. 产生GO对象,进行全局预编译

[scopes] -- GO { a:function }

  1. 全局预编译完后,调用a函数,就会产生a函数的AO对象,此时a函数预编译之后的结果

[scopes] -- aAO { num:undefined,b:function }

-- GO { a:function }

预编译a函数之后,执行a函数

执行num赋值时,num就有值了,aAO { num:10,b:function }

执行b函数调用

  1. 调用b函数时,就会产生b函数的AO对象,此时b函数预编译的结果

[scopes] -- bAO { }

-- aAO { num: 10,b:function }

-- GO { a:function }

预编译完b函数后,执行b函数

执行打印num的时候,此时bAO当中没有定义num,它就会根据作用域链到上一层aAO去找

7.原型和this指向

实例成员和静态成员

构造函数中定义的属性和方法都叫成员

实例成员

在构造函数中,通过this添加的成员(name,age)都是实例成员。

实例化成员只能通过实例化对象来调用(new出来的叫实例化对象)

var zxy = new Start('张学友',60)

静态成员

在构造函数本身上添加的成员,叫静态成员

静态成员只能通过构造函数名来访问

console.log(Start.country)

Start.country = '中国'
console.log(ldh.country) // undefined
console.log(Start.country) // 这样才能调用静态成员

静态成员作用

把一些公共的东西(毕业院校,国籍,民族等)放在静态成员中,可以减少内存分配

举例:饮水机和水杯

如果饮水机定义成实例,就相当于每一个学生都有一个饮水机,浪费资源

如果把水杯定义成静态,就相当于大家共用一个水杯,不太好。一般定义成实例的

原型的由来

解决的问题:使用this定义方法时,每new一次,都会为对象的方法开辟一块空间,浪费内存

new做的事

  1. 在堆区中开辟一块空间
  2. 给空间中的属性和方法赋值
  3. 返回空间中的首地址给栈区中的变量名

存在的问题:每创建一次对象,就会为对象里面的方法分配一个内存空间,比较浪费内存。

function Start(name,age){
  this.name = name
  this.age  = age
  // this.sing = function(){
  //     console.log('会唱歌')
}
console.dir(Start) 
// 类中有一个prototype属性,可以看做是类的静态成员,可以把公共的类定义在上面
// prototype属性一般称为原型对象
console.log(typeof Start.prototype) // object对象类型
// 使用原型对象解决内存重复占用的问题
Start.prototype.sing = function(){
  console.log('会唱歌')}

8.本地储存

概念

Web 存储 API 提供了 sessionStorage (会话存储) 和 localStorage(本地存储)两个存储对象来对网页的数据进行添加、删除、修改、查询操作。

  • localStorage 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除。(本地存储)
  • sessionStorage 用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。(会话存储)

特点

数据存在用户的浏览器中

可以方便用户设置和读取数据,刷新页面不会丢失数据

容量有限,sessionStorage 约5m,localStorage约20m

只能存储字符串,可以将对象使用JSON.stringify转成jdon后存储

存储对象方法

方法描述
key(n)返回存储对象中第 n 个键的名称
getItem(keyname)返回指定键的值
setItem(keyname, value)添加键和值,如果对应的值存在,则更新该键对应的值。
removeItem(keyname)移除键
clear()清除存储对象中所有的键

object.key(localStorage) 获取浏览器本地存储的所有键值对

// 获取文本框中的数据
var input = document.querySelector('input')

// 存储数据
document.querySelector('.c1').addEventListener('click',function(){
  sessionStorage.setItem('username',input.value)
  sessionStorage.setItem('password',input.value)
})
// 清空数据
document.querySelector('.c4').addEventListener('click',function(){
    sessionStorage.clear()
})
// 新建窗口.空白窗口,但可以看到之前存储的数据
document.querySelector('.c5').addEventListener('click',function(){
    window.open('http://网址.html') 
    // 保持ip和端口一致才能共享数据
})

会话存储特点

sessionStorage

生命周期为关闭浏览器

在同一个窗口(页面)可以数据共享

以键值对形式存储数据

localStorage

生命周期永久有效,直到手动删除或电脑报废

在多个窗口(页面)可以数据共享(仅限于同一个浏览器

以键值对形式存储数据