关闭

js 变量声明易混淆的几点知识

时间: 2018-05-15阅读: 1117标签: 变量

这是我 JavaScript 学习过程中遇到的一些容易混淆的地方,趁着有时间,做了一个整理。


变量提升

变量与函数名提升优先级

js 作用域内有变量,这个很好理解,但有一些细节需要注意。

console.log(foo);  // 函数
function foo(){
    console.log("函数声明");
}
console.log(foo); // 函数
var foo = "变量";
console.log(foo); // 变量

当变量名与函数名同名,且都提升上去了,那最终结果是哪个声明起作用呢?

有两个知识点: 
1. var foo;并不会覆盖之前的变量 
2. 函数提升优先级比变量提升要高,且不会被变量声明覆盖,但是会被变量赋值覆盖,所以上面的代码实际上是

function foo(){ // 优先级最高,提升到最前面
    console.log("函数声明");
}
var foo; // 只提升声明,不提升赋值,且不能覆盖函数声明
console.log(foo); 
console.log(foo); 
foo = "变量"; // 可以覆盖函数声明
console.log(foo); 


连等赋值的变量提升

var num1 = 1;  
function   fn(num3){  
    console.log(num1);    //output    undefined  
    console.log(num3);    //output  4  
    console.log(num4);    //throw error  “num4 is not defined”  
    console.log(num2);    //throw error  “num2 is not defined”  
    var num1 = num4 = 2;  // js 连等赋值  num4 不会被提升
    num2 = 3;             // 没有 var 会挂载到全局作用域,但不会提升,所以之前会报错
    var num3= 5;  
}  
fn(4);


if 判断内变量提升

if (true) {  
    function fn(){ return 1; }  
}else {  
     if(false){  
       function fn(){ return 2; }  
     }  
}  
console.log(fn.toString()); 
console.log(fn())

以下是从找到这个例子的原文中摘抄的内容:

chrome和ie一均为function fn(){ return 2;},而firefox中依然报错。 
可见三者处理并不相同。ff中会提前变量的声明,但不会提前块级作用域中的函数声明。而chrome和ie下就会提前块级作用域中的函数声明,而且后面的声明会覆盖前面的声明。

我写这篇文章时,测试的结果 IE10 及以下,返回2,IE11和谷歌,火狐新版,返回1

var fn;
console.log(fn); // undefined

if (true) {  
    function fn(){ return 1; }  
}else {  
     if(false){  
       function fn(){ return 2; }  
     }  
}  
console.log(fn.toString());  // 函数 1
console.log(fn()) // 1

if判断内函数并没有提升


对有名函数赋值

function test5() {
  var fn = function fn1() {
    log(fn === fn1); // true
    log(fn == fn1); // true
  }
  fn();
  log(fn === fn1); // fn1 is not defined
  log(fn == fn1);  // fn1 is not defined
}
test5();

在function内部,fn完全等于fn1

在function外面,fn1则是 not defined

!兼容

b(); 
var a = function b() {alert('this is b')}; 

在ie8及以下浏览器是可以执行b的. 说明不同浏览器在处理函数表达式细节上是有差别的.


return 后声明的提升

函数 return 语句后的变量、函数声明提升

function text6() {
   var a = 1;
   function b() {
     a = 10;
      return;
      function a() {}
    }
    b();
    console.log(a);  // 1
}
text6();


函数的作用域内赋值

在js中,提到变量赋值,就要先说作用域,而作用域,在es6之前,只有函数才会形成独立的作用域,然后函数的嵌套形成了 js 的作用域链。子作用域内可以访问父级作用域内的元素。函数的作用域在函数确定的时候就已经确定,与调用无关。

// test1
var x = 1;
function foo(x) {
  var x = 3;
  var y = function() { 
    x = 2;
    console.log(x)
  }
  y();
  console.log(x);
  return y
}
var z = foo() // 2 2
z() // 2

这段函数会输出三个 2 ,指向同一个 x,甚至,将 x 改为对象,就更明显了

// test2
var x = "abc";
function foo(x) {
  var x = c;
  var y = function() { 
    return x;
  }
  return y;
}
var c = {a:1}
var z = foo(); 
var b = z();
console.log(b === c); // true

上面例子中,foo 函数执行后,返回 y 函数并赋值给 z,z 指向 y 函数(函数体),此时,z 并不在 foo 函数的作用域内,在此作用域不能访问到 x,但 z 只是引用类型数据的一个指针,只是同 x 指向了同一个对象而已。而执行 z 函数,则会返回 x 的值,这个值是函数 y 作用域内访问到的 x 的值,是根据函数的书写位置确定的作用域,并不会因为调用位置不同,而改变变量的指向。

但是同时要注意,虽然函数作用域在函数写出来时就已经确定,但具体的值却跟调用的时机有关。

// test3
var x = "abc";
function foo(x) {
  var x = c;
  var y = function() {
    x.a++;
    return x;
  }
  return y
}
var c = {a:1}
var z = foo(); 
console.log(z()) // {a: 2}
console.log(z()) // {a: 3}
console.log(z()) // {a: 4}

这个例子中,输出的三次都是同一个对象,但输出的值不同,这是因为输出的时候的值不同,这就和调用时的实际值有关了。

同时,函数的另一个值也与函数的调用情况相关,就是 this。函数的 this 指向函数的调用者,与函数在哪里定义没有关系。

var x = "abc";
var b = {x:"def"}
function foo(x) {
  var x = c;
  var y = function() {
    x.a++;
    return this.x;
  }
  console.log(y()); // abc
  return y
}
var c = {a:1}
var z = foo(); 
console.log(z()) // abc
b.a = z;
console.log(b.a()) // def
b.b = {
    x: "hig",
    z: z
}
console.log(b);
console.log(b.b.z()); // hig

上面的例子就说明了,函数内的 this 始终指向本次调用函数的对象,如果没有,就指向全局对象,而如果指向的对象本身是另一个对象的属性,这并不影响,甚至,将上个例子里的 b.b 的 x 属性删除,最后调用时 b.b.z() 的 this 依然指向 b.b 所指的对象,即使该对象对象没有 x 值,会返回 undefined,而不会向上级寻找。

function fn(){
    var array=[];
    var b = {a:1}
    for(var i=0;i<10;i++){
        array[i]=function(){
            var abc = {def: "ghi"};
            return b;
        }
    }
    return array;
}
var a = fn();
console.log(a) //[ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
console.log(a[0].toString()) //f
console.log(a[1] === a[2]) // false
console.log(a[1] == a[2]) // false
console.log(a[1].toString() === a[2].toString()) // true
console.log(a[1]() === a[2]()) // true

两个函数相等的比较:两个函数的作用域相同并且函数内容相同,那么,比较结果为这两个函数相等,否则,不相等!不是我们直接用 == 去比较,而是按照这个规则去进行比较。

函数是对象,复合型的值。一般比较引用,同一个引用,就相等;否则不等。比较toString几乎没任何意义:因为对于函数调用来说,作用域只是一个不可见的透明的规则


对 arguments 赋值

function fn(a, b) {
    var a = 1;
    var b = 2;
    console.log(arguments[0]);
    console.log(arguments[1]);
    arguments[0] = 3;
    arguments[1] = 4;
    console.log(a);
    console.log(b);
}

fn(5,6) // 1 2 3 4 传入实参,实参和形参指向相同
fn() // unde unde 1 2 没有传入实参,arguments 指向 undefined,形参与实参不再相关
function fn(a, b) {
    'use strict'
    var a = 1;
    var b = 2;
    console.log(arguments[0]);
    console.log(arguments[1]);
    arguments[0] = 3;
    arguments[1] = 4;
    console.log(a);
    console.log(b);
}

fn(5,6) // 5 6 1 2 严格模式下 arguments 指向实参,与形参不相关
fn() // unde unde 1 2

JavaScript中Arguments对象是函数的实际参数,arguments对象的长度是由实参个数而不是形参个数决定的。形参是函数内部重新开辟内存空间存储的变量,但是其与arguments对象内存空间并不重叠。

在es5规范下的非严格模式下,函数调用时传入实参,函数内部使用 形参 与 arguments 指向相同, arguments 的具体属性改变, 形参的值也会改变,但如果没有传入实参,arguments 与 形参的指向就没有关系了。

es5规范的严格模式下,形参与arguments 分别存放,不会相关

但自es6之后,非严格模式下效果也同es5 严格模式一样,即arguments和参数符号分别存放。

而es6的严格模式下,修改arguments会报错。


函数内 形参、变量、函数 同名的问题

function aa(a,b,c) {
    console.log(arguments); // {0: 函数a, 1: 2, 2: 3} 通过更改形参的值,可以更改 arguments 
    function a(){console.log(a)}
    console.log(a); // 函数 a
    console.log(aa); // undefined, var aa 被提升
    console.log(arguments); // 同上
    var a = "ee";
    var aa = "444";
    arguments = 6;
    console.log(a); // ee
    console.log(aa); // 444
    console.log(arguments) // 6
}
aa(1,2,3)
function aa(a,b,c) {
    'use strict' // 调用严格模式
    console.log(arguments); // 指向arguments 对象,{0:1,1:2,2:3}
    function a(){console.log(a)}
    console.log(a); // 函数a
    console.log(aa); // undefined
    console.log(arguments); // 同上
    var a = "ee";
    var aa = "444";
    arguments[0] = 6;
    // arguments = 6; 严格模式 不能直接对 arguments 进行赋值
    console.log(a); // ee
    console.log(aa); // 444
    console.log(arguments) // // 指向arguments 对象,{0:6,1:2,2:3}
}
aa(1,2,3)

从网上找到的一些解释

填充变量的顺序是: 函数的形参 -> 函数声明->变量声明, 当变量声明遇到VO中已经有同名的时候,不会影响已经存在的属性。 
函数形参————由名称和对应值组成的一个变量对象的属性被创建;没有传递对应参数的话,那么由名称和undefined值组成的一种变置对象的属性性也将被创建 
函数声明————由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建;如果变置对象已经存在相同名称的厲性,则完全替换这个属性 
变量声明————由名称和对应值(undefined〉组成一个变量对象的属性被创建;如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的属性

一般情况下,会按照四种方式依次解析 
语言内置: 
形式参数: 
函数声明: 
变量声明: 
也有例外: 
内置的名称arguments表现得很奇怪,看起来应该是声明在形参之后,但是却在声明之前。这是说,如果形参里面有arguments,它会比内置的那个优先级高。所以尽可能不要在形参里面使用arguments; 
在任何地方定义this变量都会出语法错误 
如果多个形式参数拥有相同的名称,最后的那个优先级高,即便是实际运行的时候它的值是undefined;



站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/770

关闭

理解var let const区别

JavaScript中var、let、const区别?js中let和const都是es5版本新的命名规范,在此之前定定义一个变量只能用var。我们可以把let和const看做是为了弥补var的一些不足而新设计出来的

JS中this作用域的问题:常见报错:XXX function 或者变量 未定义

定义了全局的函数,但是使用的时候,报错XXX 函数或者变量未定义,但实际上js中明明已经定义了且正确;大多数是因为调用过程中this.functionname 或者this.varname中this指向的作用域问题

CSS变量使用解析

很早直接就了解到CSS变量相关的内容,奈何之前使用价值不高(很多主流浏览器不兼容),最近发现主流浏览器都已经支持了这一变化,CSS变量就像JS的变量,每个类名或者花括号就像一个function,里面的变量只有上下文以内可以获取,这就让CSS有了更多可能性。

理解Javascript的变量提升

在代码的编译期间,即代码真正执行的瞬息之间,引擎会将代码块中所有的变量声明和函数声明都记录下来。这些函数声明和变量声明都会被记录在一个名为词法环境的数据结构中。词法环境是Javascript引擎中一种记录变量和函数声明的数据结构

Js变量提升与函数提升

函数写法:函数表达式、函数声明、Function构造函数(这种不推荐).其中函数表达式不会函数提升,函数声明会函数提升。引用类型传递的是指针,指针指向了变量存储地址.这例子里,2个b变量都指向了同一个地址,所以第一次输出5,然后下面修改了函数内部变量b的指向

Js变量的扩展与解构

在ES6之前定义变量都是用var这个关键字,var是在全局的环境下声明的一个变量,而且这个变量有一个变量的提升;也就是说,即使把变量放在后面,但是JavaScript引擎的运行的时候会把变量提到代码的最上层,然后先声明再赋值

process.env前端环境变量配置教程

一个项目一般会有开发版本、测试版本、灰度版本和线上版本,每个版本会对应相同或不同的数据库、API地址。为了方便管理,我们通常做成配置文件的形式,根据不同的环境,加载不同的文件。如果手动修改代码中加载配置文件的路径也可以

let与var的区别,为什么要用let?

var是全局声明,let是块级作用的,只适用于当前代码块;var变量会发生变量提升,let则不会进行变量提升;var 会造成重复赋值,循环里的赋值可能会造成变量泄露至全局

css变量_原生css 中变量的使用

原生css 中变量的使用,这个重要的 CSS 新功能,所有主要浏览器已经都支持了。本文全面介绍如何使用它,你会发现原生 CSS 从此变得异常强大。声明变量的时候,变量名前面要加两根连词线(--),var()函数用于读取变量。

在vue中实现了在样式里使用js变量的方法

在使用vue开发时,经常会封装很多的组件方便复用,那么难免就有写样式相关组件,比如需要使用时传入颜色、高度等样式参数。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!