你真的了解Js吗?用五个问题来回顾之前的内容,留下你的答案我们一起进步!(系列十)

你真的了解Js吗(一)

JavaScript基础提升合集

🚀包含this、call、原型链、作用域等基础经典知识点
☕️每周一篇,打好基础,爬升不累
💬最全知识点解析,易懂的代码示例收藏、订阅方便阅读
💻完整版在线阅读,猛戳这里~

灵感来自Amandeep Singh大佬的文章,第一次翻译英文技术文章如果有错误欢迎大家指正!
选择这篇文章作为前端内功系列的期中总结,希望大家可以将答案写在评论区下方~

目录

一、前言

JavaScript是一种非常有趣的语言,我们都因为它的某些性质而爱上了它。
对于JavaScript来说,浏览器就是大本营,并且它们可以一起很好的为我们服务。

在Js中,有一些概念是容易被人们忽视的,甚至曾因为它们而吃过苦头。例如:原型、闭包、事件循环仍然是大部分开发者会可以绕开的“晦涩”的领域。

正如我们所知道的,不掌握细节是一件危险的事情,很可能导致你犯错。

让我们玩一个小游戏,我会问你几个问题,而你需要试着去回答它们。如果你不知道答案,或者问题超过了你的知识范围,不妨大胆的给出猜测。
记录下你的回答,并在之后检查你的答案,每答对一道题,就给自己一分,让我们开始吧!

二、开始答题

阅读完问题后大家可以停下来思考一下,再看看我的回答与分析,看看我们呼应上没有《手动狗头》~

问题一:在浏览器中,下面代码会输出什么?
var a = 10;
function foo(){
    console.log(a); // qustion
    var a = 20;
}
foo();

我的分析

上述代码等价于:

foo(){
    var a;
    console.log(a);
    a = 20
}
var a
a = 10

我的答案

输出:undefined,正如上面分析得一样:

  1. 执行foo函数,函数作用域内 a变量提升到作用域顶部
  2. 打印a,此时a变量已声明但未赋值,为undeined
  3. 函数内a变量赋值,执行结束a变量销毁
  4. 函数外变量执行

核心问题

不要慌,变量提升会在之后作为JS专题系列的文章与大家见面~

在这里插入图片描述

问题二:如果上一题中的var声明改为let声明结果会一样吗?
var a = 10;
function foo(){
    console.log(a); // qustion
    let a = 20;
}
foo();

我的分析

上述代码等价于:

foo(){
    console.log(a);
    let a = 20;
}
var a
a = 10

我的答案

输出:ReferenceError报错
典型暂时性死区问题:
函数作用域因为let的声明而成为了一个块级作用域,且let声明的变量不会提升,并且在它声明之前使用变量即报错

核心问题

  • let声明
  • 变量提升
问题三:还是和上面类似的问题,这次结果是什么?
var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

我的分析

同样是作用域问题,我们来分析一下:

  1. 全局作用域 :var name = ‘world!’; 没什么问题
  2. 立即指向函数内自成作用域:if判断为
    • var name // 此时name为undefined,声明为定义
    • if ( typeof name === ‘undefined’) 判断成立
    • name = 'Jack'
    • console.log('Goodbye' + name )

我的答案

输出:“Goodbye Jack”

核心问题

在这里插入图片描述

问题四:新数组中将包含哪些元素?
var arr = [];
for(var i = 0; i < 3; i++) {
    arr.push(() => i);
}
var newArr = arr.map(el => el());
console.log(newArr); // ??

我的分析

经典问题的变形:

  1. for循环向arr中添加了三个返回i的匿名函数
  2. arr数组遍历,分别将函数执行后的返回值返回
  3. 返回新数组

我的答案

输出:[3, 3, 3]

核心问题

问题五:下面代码的xGetter结果是什么?
var x = 10;
var foo = {
    x: 90,
    getX: function() {
        return this.x;
    }
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??

我的分析

读一遍代码:经典的this指向问题

  1. foo的属性getX保存了一个匿名函数
  2. getX中保存的一个匿名函数的引用赋值给了xGetter
  3. 这里抓住重点:
    • 当函数作为对象的方法调用时,this指向当前对象
    • 当函数作为普通函数调用时,this指向全局对象——window

这里有疑惑的同学推荐大家看一下《this、call、apply详解,系列(一)》

我的答案

输出: 10

核心问题

三、答案解析

现在,我们试着从上至下回答一下所有的问题,在揭开它们神秘的面纱过程中,我会给大家一些简要的说明。

问题一:在浏览器中,下面代码会输出什么?
var a = 10;
function foo(){
    console.log(a); // qustion
    var a = 20;
}
foo();

答案:undefined

分析:

在JavaScript中,被var关键字声明的变量会被提升,并在对应的内存中分配一个undefined,但真正的赋值操作却发生在赋值代码所在的位置,同样的var变量声明是在函数作用域中,而letconst声明的是在块级作用域中。所以下面的代码过程是这样的:

var a = 10; // 全局作用域
function foo() {
	// var 声明会被提升到当前作用域的顶部
	// 例如var a;
	console.log(a); // 输出undefined,因为它已经声明,但为定义
	// 而实际的赋值操作这在这一行才进行的 a = 20
	var a = 20; // 函数内作用域
}

核心问题:

  • 变量提升及优先级问题(下篇文章,敬请期待~)
  • 作用域
问题二:如果上一题中的var声明改为let声明结果会一样吗?
var a = 10;
function foo(){
    console.log(a); // qustion
    let a = 20;
}
foo();

答案: ReferenceError: a is not defined.

分析:

let和const允许声明者在当前(作用域)块内使用该变量。与var不同,这些变量被提升到当前作用域顶部,而是具有所谓的暂时性死区问题(TDZ)。尝试在变量声明钱中访问这些变量将引发ReferenceError,因此只能在执行到达函数声明之后对其进行访问。

var a = 10; // 全局作用域
function foo() { 
	// 进入函数作用域,并且产生暂时性死区(TDZ)
	// 在a被声明之前,会引发错误
    console.log(a); // ReferenceError
	// 暂时性死区关闭, 'a' is initialised with value of 20 here only
    let a = 20;
}

下表概述了在JavaScript中使用的不同关键字相关的提升行为作用域(credit: Axel Rauschmayer’s blog post).

问题三:还是和上面类似的问题,这次结果是什么?
var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

答案:Goodbye Jack

分析:

var声明的变量在函数作用域中进行了提升,但是它的赋值操作并没有,所以在赋值前,它被分配为undefined

问题四:新数组中将包含哪些元素?
var arr = [];
for(var i = 0; i < 3; i++) {
    arr.push(() => i);
}
var newArr = arr.map(el => el());
console.log(newArr); // ??

答案:[3, 3, 3]

分析:

在for循环中使用var关键字声明循环的下标,将为该变量创建一个存储空间,并不断更新这个存储空间存储的值

// Misunderstanding scope:thinking that block-level scope exist here
var array = [];
for (var i = 0; i < 3; i++) {
  // 箭头函数返回的确实是变量i的值
  // 他们是同一个值!!!
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]

如果你使用let来声明变量i,每一个的声明都存在不同的块级作用域中,所以个循环都会是一个新的独立的绑定。

// Using ES6 block-scoped binding
var array = [];
for (let i = 0; i < 3; i++) {
  // 这次每个i都被保存在一个独立的块中,相当于当前数据的切片
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]

解决上面的问题也可以使用闭包:

let array = [];
for (var i = 0; i < 3; i++) {
  array[i] = (function(x) {
    return function() {
      return x;
    };
  })(i);
}
const newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]
问题五:下面代码的xGetter结果是什么?
var x = 10;
var foo = {
    x: 90,
    getX: function() {
        return this.x;
    }
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??

答案:10

分析:

var x = 10; // x存在于全局作用域,等价于 window.x = 10;
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // 输出90 当函数作为对象的方法被调用时,this指向该对象
let xGetter = foo.getX; // 注意 原本的匿名函数有了别名叫xGetter,并且它被保存到了全局
xGetter(); // prints 10

要得到foo.x的值,我们需要手动绑定this的指向

let getFooX = foo.getX.bind(foo); // 将getX的作用域绑定到foo上
getFooX(); // prints 90

这里有疑惑的同学推荐大家看一下《this、call、apply详解,系列(一)》!!!

到这里,五道题就全部分析完了,如果都答对了,允许你骄傲3秒钟。这会让你了解他们背后的原理,发现自己的不足,并且更好的使用它们。如果你喜欢这篇文章,不妨点个赞,留个言吧~

参考

写在做后

在这里插入图片描述

前端内功进阶系列已经第十篇了,这是本系列的最后一篇,同时也在这里向大家预告下一系列——《Js专项专题系列》

为了能让大家参与感更加强烈,希望大家可以在评论区写下自己心中的答案,一起进步~

热门开源-欢迎star支持

关于我

  • 花名:余光
  • Writing Vue and JavaScript
  • Working at GaoDing Design
  • A console log tester

如果您看到了最后,不妨收藏、点赞、关注一下吧!您的三连就是我最大的动力,虚心接受大佬们的批评和指点,共勉!

评论 38
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

余光、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值