这是我参与更文挑战的第 20 天,活动详情查看: 更文挑战
前端小咸菜,那些年由js引发的血案
1. var 的变量提升
1.1 题目一,以下程序分别打印什么?
console.log(a);
var a = 13;
function fn(){
console.log(a);
var a = 12;
}
fn()
console.log(a)
复制代码
分析:因为var的变量提升,第一个打印为undefined,然后运行fn这个函数,第二打印的a在函数fn的作用域下,同时也是因为var的变量提升,打印undefined,第三个打印的自然是13这个没什么好解释的
1.2 题目二
var foo = 1;
function fn(){
if(!foo){
var foo = 10;
}
console.log(foo)
}
fn()
复制代码
请问这个打印什么?
分析:因为var
只有函数作用域,因此if(!foo)
,其实不是!1
而是!undefined
,因此是true
,所以打印10
2. 闭包
什么是闭包,简单的说就是函数里面嵌套函数;
2.1 题目一,打印test函数
var test = (
function(i){
return function(){
console.log(i*=2)
}
}
)(2)
test(5)
复制代码
分析:首先test等于了一个自执行函数参数为2,即相当于是这样一个代码:
test = function(i = 2){
return function(){
console.log(i = i*2)
}
}
复制代码
因此test(5),5
这个参数等于是迷惑人的,其实就是打印 2*2 = 4
2.2 题目二,A与A
let a =0, b =0;
function A(a){
A = function(b){
console.log(a + b ++)
}
console.log(a++)
}
A(1);
A(2);
复制代码
分析:首先是运行A(1)
,这样A被重写,同时打印a++
,因为a++
遵从先用后加的原则,A(1)
打印1,然后A(2)
,这里要注意A
已经被重写,此时运行打印的应该是a + b++
;因为a
在当前函数作用域没有找到因此去上一级查找,因此这里的a为2
,又入参为b=2
,因此打印结果为4
;
2.3 this和闭包综合(360笔试)
let num = 10;
let obj = {
num: 20,
};
obj.fn = (function(num){
this.num = num *3;
num++;
return function (n){
this.num += n;
num ++;
console.log(num);
}
})(obj.num)
let fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num,obj.num,window.num)
复制代码
分析:
首先 obj.fn
等于的是一个自执行函数,先执行一下,其中参数num
接受传值obj.num
则等于20,然后this.num = num*3
,即this.num = 20*3 = 60
,这里就来到了一个很重要的知识点,这个this
指的是谁?
在js中的function
的this
的指向不是定义的时候决定的而是调用者决定的,很明显上面的调用自执行函数的是window
,因此this
指向的是window
,因此,现在window.num = 60
再进入到num++
,这个num
是存在函数作用域当中的,目前值为21
,我们可以记做A作用域,然后返回出一个函数给obj.fn
,我们记做B
函数,内部作用域记B作用域,形成一个闭包;
再到let fn = obj.fn
;fn(5)
这条语句,就是执行B函数,相当于是执行window.fn(5)
,因此此时B函数里面的this,指向也是window
那么这时window.num = 60 +5 = 65
; B作用域下的num要去A作用域下去找因此打印只为 21+1 = 22
到obj.fn(10
);和let fn = obj.fn;fn(5)
其实执行都差不多,最主要的区别在于this的指向,obj.fn(10)里执行B函数,this
指向的是obj
,因此this.num += n
;就导致了obj.num = 20 + 10 = 30
,其里面的打印就是21+1=23
最后整个打印就是 10 ,30 ,65
打印的顺序是(即答案):
22
23
10,30,65
复制代码
3. 原型和原型链
3.1 基础试炼
function Fn(){
this.x = 100;
this.y = 200;
this.getX = function(){
console.log(this.x)
}
}
Fn.prototype.getX = function(){
console.log(this.x);
}
Fn.prototype.getY = function(){
console.log(this.y);
}
const f1 = new Fn();
const f2 = new Fn();
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor)
console.log(f1.constructor === Fn);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f2.__proto__.getX();
f2.getY();
Fn.prototype.getY();
复制代码
答案参考:
false
、true
、true
、fals
e、打印Fn函数体、true
、Object
100
、 undefined
、 200
、 undefined
3.2 CLASS类
看下面代码:
class Fn{
constructor(){
this.x = 100;
this.y = 200;
this.getX = function(){
console.log(this.x)
}
}
getX(){
console.log(this.x);
}
getY(){
console.log(this.y);
}
}
const f1 = new Fn();
const f2 = new Fn();
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor)
console.log(f1.constructor === Fn);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f2.__proto__.getX();
f2.getY();
Fn.prototype.getY();
复制代码
打印结果和 3.1的一样;发现没有 其实class就是一个语法糖,constructor()就是3.1里面的构造函数
3.3 关于方法定义在prototype
上的思考
从3.1和3.2的例子,我们可以清楚的发现,定义在构造函数的公共方法,在实例化对象后,他们是不相等的。
也就是说
function Fn(){
this.x = 100;
this.y = 200;
this.getX = function(){
console.log(this.x)
}
}
复制代码
其实等价于这样
function Fn(){
this.x = 100;
this.y = 200;
this.getX =new Function(){
console.log(this.x)
}
}
复制代码
那么这样子每实例化一次,就要开一个新的地址,这样其实是没必要的,解决这个问题可以把这个函数放在构造函数的外部,但是这样就有一个新的问题,但是全局作用域就被搞乱了,而prototype
就是来解决这个问题的
所以在3.1和3.2的当中,console.log(f1.getY === f2.getY)
;为true的原因