关于this, 作用域,变量提升等的学习记录

156 阅读5分钟

1. 关于this

this在JS中是一个很玄乎的东西,关于this到底是什么,网上一个比较简洁明了的说法就是函数执行时的上下文环境。但有时候依然会存在一些比较特殊的场景,让你无法一下看出this到底是什么。要想彻底弄懂各种特殊情形,就得深入原理。以下是学习阮一峰老师this的原理后,自己总结的关于this原理相关的记录。参考:阮一峰 -- this原理

代码示例1:

var x = 1;        
function foo() {            
    console.log(this.x);         
}        
foo(); // ---> 1 (1)
   
var obj = {            
    x: 2,            
    foo: foo        
};        
obj.foo(); // ---> 2 (2)

要理解this的原理,要从对象的创建开始说起,当一个对象被创建,然后将这个对象赋值给一个变量时发生了什么?比如:

代码示例2:

var obj = {
    x: 2
}; 

在上面的代码中,首先是创建了一个对象,这个对象有一个名为'x',值为2的属性,然后将这个对象赋值给变量obj,此时obj的值并不是这个对象本身,而是这个对象在内存中存储的地址。

此外,对于对象的每一个属性,都有属性描述符,这些描述符包括:属性的值 ==> [[value]], 是否可修改 ==> [[writable]], 是否可遍历 ==> [[enumerable]], 是否可配置(决定属性的描述对象是否可修改) ==> [[configurable]]。对于上面这个对象,x的value就是2。

如果对象的某个属性的值不是原始类型,而是函数,那么该属性存储的也是该函数的存储地址,而不是该函数本身,而函数是可以单独执行的,这就意味着该函数可以在不同的地方调用。

“代码示例1”的(1)处,函数单独被调用,此时如果运行环境是node,this就代表global对象;如果运行环境是浏览器,this代表window。而前面定义了全局环境下的变量x = 1,所以(1)执行时,this.x就是指全局环境的x,也就是1,所以打印的结果是1。当然这都是在非严格模式下,如果是严格模式,this是undefined。

“代码示例1”的(2)处,函数foo被赋值给obj的属性foo,此时的环境是obj,因此this.x指obj环境下的x,也就是2,所以打印结果是2。

参考:

www.ruanyifeng.com/blog/2018/0…


2. Array.prototype.slice.call()方法的使用

这个方法经常跟函数的arguments一起使用,用来将arguments转成一个真正的数组。我们知道,函数的arguments是一个类数组的集合,具有length属性,Array.prototype.slice.call()能将具有length属性的对象转成数组,如:

var a = {length:2,0:'first',1:'second'}; //有length属性,长度为2,第0个元素是first,第1个是second
console.log(Array.prototype.slice.call(a,0)); //["first", "second"]
console.log(Array.prototype.slice.call(a,1)); //["second"],第二个参数同slice()的用法,表示开始截取的起始位置

var a={0:'first',1:'second'};//去掉length属性,返回一个空数组
console.log(Array.prototype.slice.call(a,0));//[]

      function test() {
        console.log(Array.prototype.slice.call(arguments, 0));
      }
      test('a', 'b', 1, 2, 3);  //["a", "b", 1, 2, 3]

另外,使用[].slice.call(arguments, 0)实现的效果同Array.prototype.slice.call()

参考:

blog.csdn.net/i10630226/a…

3. 变量提升

js脚本在正式开始执行前,会对代码进行预编译,按一定规则重新组织好代码后才开始正式执行。预编译可以分为四步,按照这四步分析代码,就能理清变量提升的机制,步骤如下:

  1. 创建AO对象(执行期上下文);
  2. 找到函数中所有var声明的变量和形式参数,值保存为undefined;
  3. 将形式参数和实际参数对应起来;
  4. 找到所有函数声明,将函数名与函数对应起来。

实例分析:

function demo1 (a) {
    console.log(a);
    console.log(b);
    a = 10;
    var a;
    var b;
    function b(){
        console.log(a);
    }
    console.log(b);
    c = 12;
    console.log(c);
    var c = function d() {};
}
demo1(2);

第一步:函数demo1执行前,创建 空的AO: 

AO:
{ }

第二步:找形参和var,有形参a, 还有var a,形参a就相当于在函数内部声明一个变量a。 AO对象变为:

AO:
{     
    a: undefined,
    b: undefined, 
    c: undefined
}

第三步:将形参和实参对应,这里传入的参数是2,所以:

AO: 
{     
    a: 2,
    b: undefined,    
    c: undefined
}

第四步:找函数声明,这里只有b是函数声明,var c = function d() {};是函数表达式。这里函数b将原来的变量b覆盖,所以:

AO: 
{      
    a: 2,      
    c: undefined,
    b: function b(){}
}

那么上面代码的执行结果就很明显了

function demo1 (a) {
    console.log(a); // 2
    console.log(b); // function b(){ ... }
    a = 10;
    var a;
    var b;
    function b(){
        console.log(a); // 这里没有被执行到,因为函数b在代码里并没有被执行
    }
    console.log(b);  // function b(){ ... }
    c = 12;
    console.log(c);  // 12. 前面被赋值为12了
    var c = function d() {};
}
demo1(2);

4. 关于函数arguments

在js中,函数的形参个数可以和实参个数不一致,例如:

1. 实际参数少于形参时,未被传值的参数是undefined

function demo2 (a, b) {     
    console.log('a: ' +  a + ';' + ' b: ' + b);
}
demo2(3); // a: 3; b: undefined。不会报错,依然可以执行

2. 实际参数多于形参,多出的参数会被忽略

function demo2 (a, b) {  
    console.log('a: ' +  a + ';' + ' b: ' + b);
}
demo2(3, 4, 5); // a: 3; b: 4

3. 通过arguments[i]修改参数时,形参的值会同步发生改变

function demo2 (a, b) {  
    arguments[0] = 5;  
    console.log('a: ' +  a + ';' + ' b: ' + b); // a: 5; b: 4  
    console.log(a); // 5
}
demo2(3, 4);

4. 参数的值是在函数执行传值的那一刻决定的,如果当时没有给某个参数传值,那么后面通过arguments[i]也无法修改

function demo2 (a, b) {  
    arguments[1] = 5;  
    console.log('a: ' +  a + ';' + ' b: ' + b); // a: 3; b: undefined  
    console.log(b); // undefined
}
demo2(3);