基础融合-JS读代码
全局对象(Global Object, GO)和变量提升(Hoisting)机制
var func = 1
function func() {}
console.log(func + func)
这题考察的 GO,也就是全局的预编译:
- 创建 GO 对象
- 找变量声明,将变量声明作为 key,值赋为 undefined
- 找函数声明,将函数名作为 GO 对象的key,值赋为函数体
编译阶段:创建 GO 对象后,func 作为 key,值为 undefined,然后 func 变成了 函数体,所以在编译结束时,func 还是一个 function
运行阶段:func 被赋值为 1,所以 func + func 就是 2
// 阶段1:声明提升(编译阶段)
function func() {} // 函数声明优先提升
var func; // var 声明被忽略(因为 func 已由函数声明定义)
// 阶段2:执行阶段
func = 1; // var 的赋值覆盖了函数
console.log(func + func); // 1 + 1 = 2
this 的指向
let obj1 = { x: 1 }
let obj2 = obj1
obj2.y = 2
obj2 = { y: 20 }
console.log('obj1', obj1)//obj1 { x: 1, y: 2 }
['1', '2', '3'].map(parseInt) //[1, NaN, NaN]
/*
等价于==>
['1','2','3'].map((item,index)=>{
parseInt(item,index)
})
*/
parseInt
的第一个参数的每一位,都不能超过第二参数。按照x进制还原成十进制,按以下顺序执行
parseInt('1', 0)
radix 假如指定 0 或未指定,基数将会根据字符串的值进行推算。所以结果为 1。parseInt('2', 1)
radix 应该是 2-36 之间的整数,此时为 1,无法解析,返回 NaNparseInt('3', 2)
radix 是 2,表示 2 进制,注意这里不是指将其解析为 2 进制,而是按照 2 进制进行解析,但 3 不是 2 进制里的数字,所以无法解析,返回 NaN
const User = {
count: 1,
getCount: function () {
return this.count;
},
getCountArrow: () => {
return this.count; // 箭头函数的 this 指向外层作用域
},
};
console.log(User.getCount()); // 输出: 1
console.log(User.getCountArrow()); // 输出: undefined(箭头函数的 this 指向全局对象)
const func = User.getCount;
console.log(func.call(User)); // 输出: 1
console.log(func.apply(User)); // 输出: 1
this 是一个指向对象的指针,this 的指向与所在方法的调用位置有关,而与方法的声明位置无关。
作为方法调用时,this 指向调用它所在方法的对象;所以 a 为 1
作为函数调用时,this 指向 window。所以 b 为 undefined.将 User.getCount
赋值给 func
后,直接调用 func()
,this
指向全局对象
补充一点小知识:call
和apply
是立即调用函数,bind
会生成一个新的函数,不立即调用。call
参数逐个传递,apply
的参数以数组形式传递,bind
的参数可以分批传递。 适用场景来说:call
适合已知参数个数的情况,apply
适合参数个数不确定的情况,bind
适合需要延迟执行或者部分参数预填充的情况。
call、apply、bind区别
特性 | call | apply | bind |
---|---|---|---|
调用方式 | 立即调用函数 | 立即调用函数 | 返回一个新函数,不立即调用 |
参数传递 | 参数逐个传递,如 fn.call(obj, arg1, arg2) | 参数以数组形式传递,如 fn.apply(obj, [arg1, arg2]) | 参数可以分批传递,如 fn.bind(obj, arg1)``(arg2) |
使用场景 | 适合已知参数个数的情况 | 适合参数个数不确定或动态的情况 | 适合需要延迟执行或部分参数预填充的情况 |
const obj = {
f1() {
const fn = () => {
console.log('this1', this)
}
fn()
fn.call(window)
},
f2: () => {
function fn() {
console.log('this2', this)
}
fn()
fn.call(this)
},
}
1. obj.f1()
obj.f2()
- 普通函数的
this
:
由调用方式决定: -
- 直接调用时,非严格模式下
this
指向window
(严格模式下为undefined
)。 - 通过
call
/apply
/bind
可以显式绑定this
。
- 直接调用时,非严格模式下
f1的this指向obj,箭头函数自己没有this,会继承外层作用域的this。箭头函数的this无法通过apply、bind、call修改。所以,obj.f1() 打印 this1 obj this1 obj
f2是箭头函数,没有自己的this,继承外层window的this,所以f2打印了两个window
- 对象
{}
不构成作用域! - 箭头函数的外层作用域是函数/全局作用域,而不是对象字面量。
const obj = {
f2: function() { // 普通函数 f2
function fn() {
console.log(this);
}
fn();
}
};
obj.f2(); // 输出:
此时 f2
是普通函数,调用时 this
指向 obj
,但内部的 fn
仍是普通函数直接调用,this
还是 window
。对象的大括号 {}
不构成作用域,只有函数、模块、块作用域会影响箭头函数的 this
继承。只有函数、模块、块(let/const
)会形成作用域。
箭头函数 + 对象方法 + 定时器
const obj = {
name: 'Alice',
sayName() {
setTimeout(() => {
console.log(this.name);
}, 100);
},
sayName2: function() {
setTimeout(function() {
console.log(this.name);
}, 100);
}
};
obj.sayName(); // 输出?
obj.sayName2(); // 输出?
obj.sayName()
→'Alice'
(箭头函数继承sayName
的this
,即obj
)。obj.sayName2()
→undefined
(普通函数的this
默认指向window
,浏览器中window.name
为空)。
箭头函数 + 构造函数
function Person(name) {
this.name = name;
this.sayName = () => {
console.log(this.name);
};
}
const person = new Person('Bob');
person.sayName(); // 输出?
const fn = person.sayName;
fn(); // 输出?xxx
person.sayName()
→'Bob'
(箭头函数继承构造函数调用时的this
,即新创建的实例)。fn()
→'Bob'
(箭头函数的this
绑定后无法修改,仍指向实例)。
若将箭头函数改为普通函数,fn()
的输出是什么?
(答案:undefined
,普通函数的 this
默认指向全局。)
箭头函数 + 原型链
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = () => {
console.log(this.name);
};
const cat = new Animal('Tom');
cat.speak(); // 输出? xxx
cat.speak()
→undefined
(箭头函数定义在全局,this
继承全局作用域的window
)。- 关键:箭头函数不适合用作原型方法,因为它无法动态绑定实例的
this
。
箭头函数 + 事件监听
const button = document.getElementById('myButton');
button.addEventListener('click', (event) => {
console.log(this); // 输出?
});
button.addEventListener('click', function() {
console.log(this); // 输出?
});
- 箭头函数 →
window
(继承全局作用域的this
)。 用event.currentTarget
替代this
- 普通函数 →
button
(事件回调的this
默认指向触发事件的元素)。
箭头函数 + 类(Class)
class Counter {
count = 0;
increment = () => {
this.count++;
console.log(this.count);
};
decrement() {
this.count--;
console.log(this.count);
}
}
const counter = new Counter();
const inc = counter.increment;
const dec = counter.decrement;
inc(); // 输出?
dec(); // 输出?
inc()
→1
(箭头函数绑定实例的this
)。dec()
→ 报错(普通方法的this
默认指向全局,this.count
为undefined
)。
如何修正 decrement
方法使其不报错?
(答案:用 bind
绑定实例:this.decrement = this.decrement.bind(this);
)
箭头函数 + 嵌套对象
const game = {
level: 1,
start: {
tutorial: () => {
console.log(this.level);
},
mission: function() {
console.log(this.level);
}
}
};
game.start.tutorial(); // 输出?xxxx
game.start.mission(); // 输出?
tutorial()
→undefined
(箭头函数的外层是全局作用域,this
指向window
)。mission()
→undefined
(普通函数的this
指向调用者start
对象,start
没有level
属性)。
变体:
如何让 tutorial
输出 game.level
?
(答案:改用普通函数 + 显式绑定:tutorial: function() { console.log(game.level); }
)
const obj = {
foo: () => console.log(this === window),
bar: function() {
(() => console.log(this === obj))();
}
};
obj.foo(); // 输出?
obj.bar(); // 输出?
obj.foo()
→true
(箭头函数继承全局this
)。obj.bar()
→true
(箭头函数继承bar
的this
,即obj
)