这是我 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变量声明...

    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. [js]变量声明、函数声明、函数定义式、形参之间的执行顺序

    一.当函数声明和函数定义式(变量赋值)同名时 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机制(js变量声明提升机制)

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

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

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

随机推荐

  1. pytorch 文本输入处理

    https://blog.csdn.net/nlpuser/article/details/88067167 https://blog.csdn.net/u012436149/article/deta ...

  2. centos ntfs-3g 安装和使用

    安装fuse 下载fuse(ntfs-3g依赖fuse):http://vdisk.weibo.com/s/ajww5fZsUq50L?from=page_100505_profile&wvr ...

  3. c# 设计模式 之:工厂模式之---工厂模式

    1.uml类图: 实现和依赖关系: 实现: SportFactory.JeepFactory.HatchbackFactory 实现 IFactory 接口 SportCar.JeepCar.Hatc ...

  4. 平台支持的从经典部署模型到 Azure Resource Manager 的 IaaS 资源迁移

    本文介绍如何才能将基础结构即服务 (IaaS) 资源从经典部署模型迁移到 Resource Manager 部署模型. 用户可以阅读有关 Azure Resource Manager 功能和优点的更多 ...

  5. models的单表操作

    orm创建数据库 from django.db import models # Create your models here. class Book(models.Model): name = mo ...

  6. Python学习---Django下的Sql性能的测试

    安装django-debug-tools Python学习---django-debug-tools安装 性能测试: settings.py INSTALLED_APPS = [ ... 'app01 ...

  7. Shell使用手册

    1.循环数组 list=(20180531 20180430 20180331 20180228 20180131 20171231 20171130 20171031 20170930 201708 ...

  8. December 25th 2016 Week 53rd Sunday

    Patience is bitter, but its fruit is sweet. 忍耐是痛苦的,但它的果实是甜蜜的. What can we do if there is no fruit of ...

  9. codeforces 811E Vladik and Entertaining Flags(线段树+并查集)

    codeforces 811E Vladik and Entertaining Flags 题面 \(n*m(1<=n<=10, 1<=m<=1e5)\)的棋盘,每个格子有一个 ...

  10. Id vs Instancetype

    我写了一篇 Id vs Instancetype的文章,欢迎访问!