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

时间:2022-10-25 22:22:48

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

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

  1. js 变量声明 (var使用与不使用的区别)

    js 变量声明 (var使用与不使用的区别) 一.总结 一句话总结:不使用var声明变量的时候,变量是全局对象(window对象)属性,在全局中使用var声明变量是全局变量 var 全局变量 局部变量 ...

  2. Js 变量声明提升和函数声明提升

    Js代码分为两个阶段:编译阶段和执行阶段 Js代码的编译阶段会找到所有的声明,并用合适的作用域将它们关联起来,这是词法作用域的核心内容 包括变量声明(var a)和函数声明(function a(){ ...

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

    1.Array类型函数 array.concat(item...) 函数功能:关联数组,实现数组相加功能,但并不影响原先数组,concat返回新数组. array.join(separator) 函数 ...

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

    先来两个问题 很多时候,在直觉上,我们都会认为JS代码在执行时都是自上而下一行一行执行的,但是实际上,有一种情况会导致这个假设是错误的. a = 2; var a; console.log(a); 按 ...

  5. JS变量声明提升和函数声明提升

    JS代码在执行的时候会先找出执行代码中定义的变量和函数,对其进行声明. 例1:console.log(a); var a = 4; 此时输出undefined.a变量在执行console.log(a) ...

  6. &lbrack;js&rsqb;变量声明、函数声明、函数定义式、形参之间的执行顺序

    一.当函数声明和函数定义式(变量赋值)同名时 function ledi(){ alert('ledi1'); }; ledi(); var ledi = function (){ alert('le ...

  7. js变量声明与赋值以及函数声明

    if (!("a" in window)) { var a = 1; } alert(a); 结果:undefined 分析: 首先,所有的全局变量都是window的属性,语句 v ...

  8. 解读JavaScript中的Hoisting机制&lpar;js变量声明提升机制&rpar;

    hoisting机制:javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面. 知识点一:javascript是没有 ...

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

    变量声明: JavaScript是一种弱类型语言,它的变量类型由它的值来决定,var是变量声明. 变量类型: 基本类型:number.string.boolean(布尔类型:var a=true/fa ...

随机推荐

  1. Spring配置文件解析--集合注入方法

    <bean id="moreComplexObject" class="example.ComplexObject"> <property n ...

  2. POJ 3159 Candies(差分约束)

    http://poj.org/problem?id=3159 题意:有向图,第一行n是点数,m是边数,每一行有三个数,前两个是有向边的起点与终点,最后一个是权值,求从1到n的最短路径. 思路:这个题让 ...

  3. html5 canvas 画板

    <!doctype html> <head> <meta http-equiv="Content-Type" content="text/h ...

  4. linux makefile中一些复制运算的区别

    Makefile 中  :=. ?= .+= .=的区别 = 是最基本的赋值:= 是覆盖之前的值?= 是如果没有被赋值过就赋予等号后面的值,如果已经被赋值则就用之前的赋值+= 是添加等号后面的值

  5. 【codeforces 438D】The Child and Sequence

    [原题题面]传送门 [大致题意] 给定一个长度为n的非负整数序列a,你需要支持以下操作: 1:给定l,r,输出a[l]+a[l+1]+…+a[r]. 2:给定l,r,x,将a[l],a[l+1],…, ...

  6. iOS safari 苹果手机如何阻止页面弹性&OpenCurlyDoubleQuote;橡皮筋效果”?

    苹果上这个上下弹的效果对于有固定导航的页面,体验很不好 知乎上搜到一个并不是完美方案:https://www.zhihu.com/question/22256539 完美方案是有个国外插件叫:prev ...

  7. 元素class的增、删、查、toggle

    比如有一个元素div <div class="btn user">我是div</div> 之前只知道元素有一个className可以来改动  元素的类名 但 ...

  8. AMR11A - Magic Grid

    Thanks a lot for helping Harry Potter in finding the Sorcerer's Stone of Immortality in October. Did ...

  9. Java第15章笔记

    字符串的概述 1.什么是字符串:零个或多个字符组成的有限序列 2.如何使用字符串:(使用字符串分为两步)          1)定义并初始化字符串          2)使用字符,对字符串进行一些处理 ...

  10. 測试AtomicInteger与普通int值在多线程下的递增操作

    日期: 2014年6月10日 作者: 铁锚 Java针对多线程下的数值安全计数器设计了一些类,这些类叫做原子类,当中一部分例如以下: java.util.concurrent.atomic.Atomi ...