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

这是我 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;

资料:

https://segmentfault.com/q/1010000006727575?_ea=1111028

http://www.cnblogs.com/sharpxiajun/archive/2011/09/16/2179010.html

https://www.cnblogs.com/zhouyongtao/archive/2012/11/22/2783089.html

https://www.cnblogs.com/luqin/p/5164132.html

https://segmentfault.com/q/1010000006135524

https://www.cnblogs.com/Eric1997/p/7499819.html

原文地址:https://www.cnblogs.com/agyyl/p/8977913.html

时间: 2024-10-15 17:36:22

js 变量声明易混淆的几点知识的相关文章

js常用函数、书写可读性的js、js变量声明...

1.Array类型函数 array.concat(item...) 函数功能:关联数组,实现数组相加功能,但并不影响原先数组,concat返回新数组. array.join(separator) 函数功能:将array中的每个元素用separator为分隔符构造成字符串,默认的separator是逗号,当然你可以自己指定separator 事实上join方法最常用的地方在字符串相加,用过js的人都知道js的字符串相加是特别慢且性能特别差的,把大量片段字符串放在一个数组中并用join方法连接比用+

浅谈JS变量声明和函数声明提升

先来两个问题 很多时候,在直觉上,我们都会认为JS代码在执行时都是自上而下一行一行执行的,但是实际上,有一种情况会导致这个假设是错误的. a = 2; var a; console.log(a); 按照传统眼光,console.log(a)输出的应该是undefined,因为var a在a = 2之后.但是,输出的是2. 再看第二段代码: console.log(a); var a = 2; 有人会想到第一段代码,然后回答undefined.还有人会认为a在使用前未被声明,因此抛出Referen

js变量声明提升

1.变量提升 根据javascript的运行机制和javascript没有块级作用域这个特点,可以得出,变量会声明提升移至作用域 scope (全局域或者当前函数作用域) 顶部的. 变量声明提升至全局域 console.log(a); // undefined var a ="Hi"; 相当于 var a; // 变量提升到全局作用域 console.log(a); // 已声明变量a,但未初始化,固为undefined a="Hi"; 变量声明提升至当前函数域 v

[js]变量声明、函数声明、函数定义式、形参之间的执行顺序

一.当函数声明和函数定义式(变量赋值)同名时 function ledi(){ alert('ledi1'); }; ledi(); var ledi = function (){ alert('ledi2'); }; ledi(); 执行顺序: var ledi: function ledi(){ alert('ledi1'); }; //预编译结束 ============== ledi(); ledi = function (){ alert('ledi2'); }; ledi(); 函数

JS——变量声明、变量类型、命名规范

变量声明: JavaScript是一种弱类型语言,它的变量类型由它的值来决定,var是变量声明. 变量类型: 基本类型:number.string.boolean(布尔类型:var a=true/false;).undefined(未定义类型: var a;).null(空对象类型var ) 复合类型:object(对象类型) 命名规范: 1)区分大小写 2)第一个字符必须是字母.下划线.美元符号$ 3)其他字符可以是字母.下划线.美元或者数字 原文地址:https://www.cnblogs.

[Effective JavaScript 笔记] 第12条:理解变量声明提升

js支持词法作用域,即除了极少的例外,对变量的引用会被绑定到声明变量最近的作用域中. js不支持块级作用域,即变量定义的作用域并不是离其最近的封闭语句或代码块,而是包含它们的函数. 不了解这个会产生一些微妙的bug. functon isWinner(palyer,others){ var highest=0; for(var i=0,n=others.length;i<n;i++){ var palyer=others[i]; if(player.score>highest){ highes

声明提前js变量

声明提前问题相关 js的变量声明语句无论出现在何处,都会先与其他代码首先被执行,使用var声明关键词声明变量的 作用于是当前的执行上下文,有可能是外围函数,或者,当变量声明在函数体之外时,则为全局变量 想一个未声明变量复制会隐式地转换为全局变量(它编程了库对象的一个属性)声明变量与未声明变量之间的区别为: 1,声明变量的作用范围限定在其执行的上下文环境中,未生名的变量总是全局的 2.声明变量在其他代码执行之前创建,未声明的变量在其赋值语句执行之前都是不存在的 3.声明变量是执行上下文(函数或者全

2015.7.15 第七课 课程重点(js、声明变量、数据类型)

1.vs新建步骤 文件——新建项目——web——空白web应用程序——取名.位置.确定——此时软件会自动生成一个sln文件(解决方案),一解决方案下面可以有多个项目. 点击项目——新建文件夹css和js和image——新建html页面取名demo1 (推荐设置:工具——选项——可以设置下字体和颜色:文本编辑器——把常用的几种语言设上行号颜色提示) 2.什么是js JavaScript是一种脚本语言,结构简单,使用方便,其代码可以直接放入HTML文档中,可以直接在支持JavaScript的浏览器中

【repost】 JS变量重复声明以及忽略var 声明的问题及其背后的原理

JS的容错率很高,一些其他语言常见的小错误JS都能大度得包容,比如给一个方法传入超出预计的参数.在声明变量之前使用该变量(变量的声明提升解决了这个问题)等等,这里我们就要解剖一下JS变量重复声明以及当我们忽略var使用 a=2来声明变量时a为全局变量的问题: [javascript] view plain copy //第一段代码 var a = 2; var a = 3; alert(a);//3 //第二段代码 <span style="font-size:18px;">