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()
。
参考:
3. 变量提升
js脚本在正式开始执行前,会对代码进行预编译,按一定规则重新组织好代码后才开始正式执行。预编译可以分为四步,按照这四步分析代码,就能理清变量提升的机制,步骤如下:
- 创建AO对象(执行期上下文);
- 找到函数中所有var声明的变量和形式参数,值保存为undefined;
- 将形式参数和实际参数对应起来;
- 找到所有函数声明,将函数名与函数对应起来。
实例分析:
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);