一. 全局变量和局部变量分析
  1. 在函数外部由var定义的变量称为全局变量,变量的作用范围是整个程序,只有当程序运行完后(在浏览器中一般指关掉这个页面)才会释放其内存空间。

    不用var声明的变量称为隐式全局变量,注意在函数内部不用var定义的变量也是隐式全局变量。

    注意用var声明的变量(无论是全局变量还是局部变量)是不能用delete删除的,因为var定义的变量是不可配置的。但是隐式全局变量是可以利用delete删除的,因为它是可配置的。
  1. function f(){
  2. var x = 1;//在函数内部用var定义,它是一个局部变量
  3. }
  4. console.log(x);//会报错,x没有被定义
  1. function f(){
  2. x = 1;//在函数内部定义,但它是一个隐式全局变量
  3. }
  4. console.log(x);//输出1
  1. 在函数内部定义的变量称为局部变量, 变量的作用范围只在定义的这个函数内部,像上面的第一个程序,在函数内部定义了x,但是函数外部在程序台输出x时会报错,提示x没有被定义。delete运算符同样也不能删除var 定义的局部变量,因为var定义的变量是不可配置的。
  2. 全局变量和局部变量是允许同名的,但是在函数内部局部变量会覆盖全局变量,如下例所示:
  1. var x = 100;//定义全局变量x并初始化为100
  2. function f(){
  3. var x = 10;//定义局部变量x并初始化为10
  4. console.log(x);//输出10,全局变量被局部变量覆盖
  5. }
  6. console.log(x);//输出100
二. 从两个角度去理解变量作用域
  • 变量的作用域:作用指的是变量能够起作用,域:指的是区域,范围。连起来作用域指的就是变量能够起作用的范围。

  1. 第一种是直观地通过变量定义所在的位置去理解变量作用域,变量定义在函数外部那么变量的作用域就是整个程序,变量定义在函数内部,那么变量作用域就是这个函数的内部。
  2. 我们知道当var 声明一个全局变量时,实际上是定义了全局变量的一个属性。js全局变量是全局对象的属性,这是在ES规范中强制规定的。对于局部变量则没有此规定,但是我们可以推断出,局部变量是跟函数调用相关的某个对象的属性,比如定义一个函数f它有一个局部变量x,这个局部变量x就和函数(对象,js中函数也是对象)f1有关,它是对象f1的一个属性。
  1. var x = 10; //定义一个全局变量并初始化为10
  2. function f(){ //定义一个函数实际上是开辟一段内存空间,然后f指向这段内存空间,实际上f是这段
  3. //内存空间的引用(按c语言来理解f中存放的是这段内存空间的首地址,但是不是一回事,因为c中要想访
  4. //这段内存空间的内容需要*f,而js中直接通过f就可以访问),通过f我们就直接可以访问这段内存空间里
  5. //内容
  6. var x = 0;//定义一个局部变量
  7. console.log(x);
  8. }
  9. var f1 = f;//将函数f的引用赋给f1,此时f1和f同时引用这个函数。
  10. f1();//调用上面那个函数
  11. console.log(f);//访问这个函数的内容
  12. console.log(f1);//访问这个函数的内容
  13. console.log(typeof(f));//function类型
  14. console.log(typeof(f1));//function类型

如果将一个局部变量看成是某个对象的属性的话,我们就可以换一个角度来理解变量作用域每一段js代码都有一个与之相关联的作用域链,作用域链就是一个对象列表或链表。这组对象定义了这段代码作用域中的变量。以上面这段程序为例,下面是作用域链:



js引擎解析标识符的过程:

当js在函数内部需要查找变量x的时候会先从对象链表中的第一个对象开始查找,如果这个对象有一个名为x的属性,那么则会直接使用这个属性的值,如果没有这个属性,那么js会接着查找对象链表中的下一个对象。以此类推,如果作用域链上没有任何一个对象含有属性x,那么js认为这段代码的作用域链上不存在x,并最终抛出一个引用错误异常。

在此程序中,要查找变量x,在作用域链表上第一个对象中就能够查找到,并将其返回。

下面介绍一下不同位置代码的作用域链:

  • 在js的最顶层代码中,作用域链是由一个全局对象组成。

    还是上面那个程序,红色的那些代码就是顶层代码,因为它们不在某个函数的内部,相应的作用域链见下图:

    //红色的代码是最顶层代码

    var x = 10; //定义一个全局变量并初始化为10

    function f(){

    var x = 0;//定义一个局部变量

    console.log(x);

    }

    var f1 = f;//将函数f的引用赋给f1,此时f1和f同时引用这个函数。

    f1();//调用上面那个函数

    console.log(f);//访问这个函数的内容

    console.log(f1);//访问这个函数的内容

    console.log(typeof(f));//function类型

    console.log(typeof(f1));//function类型



    最顶层代码的作用域链(它只有一个全局对象):

  • 在不包含嵌套的函数体内的代码,作用域上有两个对象,第一个是这个函数对象,另一个就是全局对象。

    //绿色的代码是函数体内的代码

    var x = 10; //定义一个全局变量并初始化为10

    function f(){

    var x = 0;//定义一个局部变量

    console.log(x);

    }

    var f1 = f;//将函数f的引用赋给f1,此时f1和f同时引用这个函数。

    f1();//调用上面那个函数

    console.log(f);//访问这个函数的内容

    console.log(f1);//访问这个函数的内容

    console.log(typeof(f));//function类型

    console.log(typeof(f1));//function类型



    不包含嵌套的函数体内的代码的作用域链为:

  • 在包含嵌套的函数体内的代码中,作用域链上至少有三个对象。

  • 这里以嵌套一次为例,f函数中嵌套着ff函数,如下面的程序:

    //紫色的代码是ff函数体内的代码

    var x = 10; //定义一个全局变量并初始化为10

    function f(){

    var x = 0;//定义一个局部变量

    console.log(x);



    function ff(){

    var y = 20;

    console.log(y);

    }



    ff();

    }

    var f1 = f;//将函数f的引用赋给f1,此时f1和f同时引用这个函数。

    f1();//调用上面那个函数

    console.log(f);//访问这个函数的内容

    console.log(f1);//访问这个函数的内容

    console.log(typeof(f));//function类型

    console.log(typeof(f1));//function类型



    ff函数体内代码的作用域链为:

  • 一些小启发

  • 通过上面的介绍,大家可能就会理解为什么最顶层的代码不能访问局部变量了,因为在最顶层的代码作用域链中根本只有一个全局对象,js引擎在查找变量时就在这个全局对象中去查找相应的属性。

  • 还有我们在学js的变量的章节时,会告诉我们尽量使用局部变量,为什么呢?在这里也能答案,在一个函数内部访问某个变量,这个变量在作用域链的位置越深,js引擎需要查询的次数就越多。当这个变量就在这个函数内部定义时,js引擎在查找作用域链的时候,第一次查询就能够查找到了,速度肯定要快一些。

  • 函数创建时,产生内部属性[[Scope]]包含函数被创建的作用域中对象的集合(作用域链)

    作用域链上每个对象称为可变对象(Variable Obejct),

    每一个可变对象都以键值对形式存在(VO要细分的话,全局对象GO和活动对象AO)

  • 函数执行时,创建内部对象叫做执行环境/执行上下文(execution context)

    它定义了一个函数执行时的环境,函数每次执行时的执行环境独一无二

    函数执行结束便会销毁

  • js引擎就通过函数执行上下文的作用域链规则来进行解析标识符(用于读写),从作用域链顶端依次向下查找

  • 尽量使用局部变量,降低作用域查找性能开销

三. 关于浮点数值计算产生舍入误差的问题分析
  • 在js中0.1+0.2并不等于0.3,而是等于0.30000000000000004,真的不可思议,计算机算出来的结果还会有错呀。这其实是由于js中的number类型是使用IEEE754格式来表示整数和浮点数值。
  • IEEE754是一个二进制表示法,可以精确地表示分数,比如1/2,1/4,1/8和1/1024.但遗憾的是,我们常用的分数(特别是在金融计算方面)都是十进制分数,比如1/10,1/100,等。二进制表示法并不能精确地表示类似0.1这样简单的数字。
  • js的数字具有足够的精度,并且可以极其近似于0.1,但是在编程时会遇到一些问题,比如你想判断两个数的和是不是0.3,这就出问题了,如下面的代码,if条件就不能满足,所以我们在写代码时要避免这种比较两个数值是否相等的情况:
  1. var x = 0.1;
  2. var y = 0.2;
  3. if(x+y==0.3){
  4. console.log(x+"+"+y+"=0.3");//不会输出,因为if条件不能满足
  5. }
  • 其中的一个解决方法就是尽量避免使用浮点数,比如在金融行业中,分作为最小单位时,使用整数表示分,而不是使用小数,这样就可以避开小数了。也就是说当你知道你要计算的某个数的的小数位数时可以先乘上10的多少次方将其转换为整数然后最终的结果再除以这个乘上的数即可。
四. 理解js预解析
  • 先上一段代码,看看能不能写出最终的执行结果.
  1. console.log(a);
  2. var a = 1;
  3. console.log(a);
  4. function a(){
  5. console.log(2);
  6. }
  7. var a = 3;
  8. console.log(a);
  9. function a(){
  10. console.log(4);
  11. }
  12. console.log(a);
  13. a();



解释一下:

  1. 首先预解析阶段遇到 var a = 1;声明变量a
  2. 然后遇到第一个函数a声明,这时由于和变量a重名,故将变量a替代,此时a表示一个函数
  3. 然后遇到var a = 3;变量a干不过函数a,a代表的还是一个函数
  4. 最后又遇到第二个函数a声明,后面函数a声明替换前面的,a还是代表一个函数但是函数体内容发生了变化
  • 注意: 预解析阶段的变量可以理解为都被赋值为undefined,函数就是函数体的内容,当遇到变量名a和函数名a一致时,预解析完后a是函数a,里面存放的是函数体的内容,函数a和函数a同时出现时,后面声明的函数还会覆盖前面声明的函数。

故:

  1. console.log(a); //输出函数a,函数体内容是第二个函数声明
  2. var a = 1; //经过赋值之后,函数a变为了变量a,并且值为1
  3. console.log(a);//1
  4. function a(){
  5. console.log(2);
  6. }
  7. var a = 3;//变量a变为3
  8. console.log(a);//3
  9. function a(){
  10. console.log(4);
  11. }
  12. console.log(a);//3
  13. a();//a现在是一个变量,当然会报错
  • 再举一个小例子:
  1. var a =1;
  2. function fn(a){
  3. console.log(a)
  4. a = 2;
  5. }
  6. fn():
  7. console.log(a);
  • 运行结果:

  • 主要想说明的一点就是function fn(a) 就相当于function fn(var a),因此预解析阶段是有对这个函数内部的a的预解析的,因此在函数内部输出的a为undefined而没有报错。
  • 将上面的例子稍微改动一下
  1. var a =1;
  2. function fn(a){
  3. console.log(a)
  4. a = 2;
  5. }
  6. fn(a): //将a的值传递进去
  7. console.log(a);
  • 运行结果:

  • 将a当作实参传递进去之后,局部变量a的值变为1,再函数内部将a变为2,但是在外部输出时输出的是全局作用域下的a,因此还是1.
  • 注意在函数内部a=2;当没有变量a的声明时,那么a=2生成的是全局作用域下的a,但是当函数内部有局部作用域下的a时,a=2仅仅就是赋值的作用

更新中…

本人是个小白,如有错误欢迎指正…

类型,值,变量知识总结(js)的更多相关文章

  1. JavaScript权威设计--JavaScript类型,值,变量(简要学习笔记三)

    1.负号是一元求反运算 如果直接给数字直接量前面添加负号可以得到他们的负值     2.JavaScript中的运算超出了最大能表示的值不会报错,会显示Infinity. 超出最小也不报错,会显示-I ...

  2. 多动手试试,其实List类型的变量在页面上取到的值可以直接赋值给一个js的Array数组变量

    多动手试试,其实List类型的变量在页面上取到的值可以直接赋值给一个js的Array数组变量,并且数组变量可以直接取到每一个元素var array1 = '<%=yearList =>'; ...

  3. js基础知识--变量类型和变量计算

    提问: JS中使用typeof能得到的哪些类型 何时使用===何时使用== JS中有哪些内置函数 JS变量按照存储方式区分为哪些类型,并描述其特点 如何理解JSON 涉及知识点:(1)变量类型 值类型 ...

  4. JS的基本类型(小知识)

    一:js中的基本类型: 基本类型:boolen, string ,number,null,undefined 引用类型:object,和函数 二.undedifned和null的区别: 1 undef ...

  5. C#的类型、变量和值

    大学学了C#,工作也是使用C#,虽然在日常的开发中没什么大的问题,但个人觉得在C#的理解还不是很清晰,所以决定花一定的时间来理一理学过的知识,顺便革新下脑袋里的知识,因为坑爹的学校在教.net的时候, ...

  6. js声明引入和变量声明和变量类型、变量

    问题: 在网页的发展历程中,发现网页不能对用户的数据进行自动校验,和提供一些特效. 解决: 使用javascript. 作用 可以让网页和用户进行直接简单的交互. 可以让网页制作特效和动画. 声明js ...

  7. golang中值类型/指针类型的变量区别总结

    转自:https://segmentfault.com/a/1190000012329213 值类型的变量和指针类型的变量 先声明一个结构体: type T struct { Name string ...

  8. python限定方法参数类型、返回值类型、变量类型等

    typing模块的作用 自python3.5开始,PEP484为python引入了类型注解(type hints) 类型检查,防止运行时出现参数和返回值类型.变量类型不符合. 作为开发文档附加说明,方 ...

  9. golang基础--类型与变量

    基础知识--类型与变量 基本类型 布尔型:bool 长度: 1字节 取值范围: false, true 注意事项: 不可以使用数字代表,不像 python中可是使用 1和0表示 整型: int/uin ...

  10. 【前端知识体系-JS相关】JS基础知识总结

    1 变量类型和计算 1.1 值类型和引用类型的区别? 值类型:每个变量都会存储各自的值.不会相互影响 引用类型:不同变量的指针执行了同一个对象(数组,对象,函数) 1.2 typeof可以及检测的数据 ...

随机推荐

  1. nvidia gtx1050在kali linux系统下安装显卡驱动,且可以使用x-setting切换显卡

    转自:https://www.zzhsec.com/255.html 1.更换源[使用中科大或者官方源都可以] 下面使用中科大的源 root@Andy:/home/dnt# vi /etc/apt/s ...

  2. 2019-9-9:渗透测试,docker下载dvwa,使用报错型sql注入dvwa

    docker下载dvwa镜像,报错型注入dvwa,low级 一,安装并配置docker 1,更新源,apt-get update && apt-get upgrade &&am ...

  3. GitHub 标星 1.6w+,我发现了一个宝藏项目,作为编程新手有福了!

    大家好,我是 Rocky0429,一个最近老在 GitHub 上闲逛的蒟蒻... 特别惭愧的是,虽然我很早就知道 GitHub,但是学会逛 GitHub 的时间特别晚.当时一方面是因为菜,看着这种全是 ...

  4. Spring与Redis整合(spring-data-redis)

    maven依赖 <properties> <!-- redis 版本 --> <redis.version>2.9.0</redis.version> ...

  5. Theano中的导数

    计算梯度 现在让我们使用Theano来完成一个稍微复杂的任务:创建一个函数,该函数计算相对于其参数x的某个表达式y的导数.为此,我们将使用宏T.grad.例如,我们可以计算相对于的梯度 import ...

  6. Css搭建

    教你做css比较好的网站: https://www.jianshu.com/p/23b2bfc9a90d?tdsourcetag=s_pcqq_aiomsg https://cloud.tencent ...

  7. Java基础IO类之缓冲流

    首先要明确一个概念: 对文件或其他目标频繁的读写操作,效率低,性能差. 使用缓冲流的好处是:能够高效的读写信息,原理是先将数据先缓冲起来,然后一起写入或者读取出来. 对于字节: BufferedInp ...

  8. SpringBoot实现登录

    1.使用Spring Initializer快速创建Spring Boot项目 1.1 IDEA:使用 Spring Initializer快速创建项目 IDE都支持使用Spring的项目创建向导快速 ...

  9. Day01-初识 Python

    1.CPU/内存/硬盘/操作系统 CPU :计算机的运算和处理中心,相当于人类的大脑. 内存 :暂时存储数据,临时加载数据应用程序. 硬盘 :长期存储数据. 操作系统:一个软件,连接计算机的硬件与所有 ...

  10. List接口下的集合

    集合框架 List接口下的集合特点: Set接口下的集合特点: 1.都是有序的 1.都是无序的 2.都有下标 2.没有下标 3.都可以重复 3.不可重复(覆盖) List接口下的集合 1.ArrayL ...