前端小咸菜,那些年由js引发的血案

这是我参与更文挑战的第 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中的functionthis的指向不是定义的时候决定的而是调用者决定的,很明显上面的调用自执行函数的是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();
复制代码

答案参考:
falsetruetruefalse、打印Fn函数体、trueObject

100undefined200undefined

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的原因

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享