资讯专栏INFORMATION COLUMN

快速掌握JavaScript面试基础知识(二)

fyber / 1681人阅读

摘要:第一部分请点击快速掌握面试基础知识一闭包闭包由一个函数以及该函数定义是所在的环境组成。当匿名函数执行的时候,的值为。这个问题可以改用后面会介绍方法来解决,通过对每一个匿名函数构建独立的外部作用域来实现。

译者按: 总结了大量JavaScript基本知识点,很有用!

原文: The Definitive JavaScript Handbook for your next developer interview

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

根据StackOverflow调查, 自2014年一来,JavaScript是最流行的编程语言。当然,这也在情理之中,毕竟1/3的开发工作都需要一些JavaScript知识。因此,如果你希望在成为一个开发者,你应该学会这门语言。

这篇博客的主要目的是将所有面试中常见的概念总结,方便你快速去了解。(鉴于本文内容过长,方便阅读,将分为三篇博客来翻译, 此为第二部分。第一部分请点击快速掌握JavaScript面试基础知识(一))

闭包

闭包由一个函数以及该函数定义是所在的环境组成。我们通过例子来形象解释它。

function sayHi(name){
  var message = `Hi ${name}!`;
  function greeting() {
    console.log(message)
  }
  return greeting
}
var sayHiToJon = sayHi("Jon");
console.log(sayHiToJon)     // ƒ() { console.log(message) }
console.log(sayHiToJon())   // "Hi Jon!"

请理解var sayHiToJon = sayHi("Jon");这行代码的执行过程,sayHi函数执行,首先将message的值计算出来;然后定义了greeting函数,函数中引用了message变量;最后,返回greeting函数。
如果按照C/Java语言的思路,sayHiToJon就等价于greeting函数,那么会报错:message未定义。但是在JavaScript中不一样,这里的sayHiToJon函数等于greeting函数以及一个环境,该环境中包含了message。因此,当我们调用sayHiToJon函数,可以成功地将message打印出来。因此,这里的闭包就是greeting函数和一个包含message变量的环境。(备注: 为了便于理解,此段落未按照原文翻译。)

闭包的一个优势在于数据隔离。我们同样用一个例子来说明:

function SpringfieldSchool() {
  let staff = ["Seymour Skinner", "Edna Krabappel"];
  return {
    getStaff: function() { console.log(staff) },
    addStaff: function(name) { staff.push(name) }
  }
}

let elementary = SpringfieldSchool()
console.log(elementary)        // { getStaff: ƒ, addStaff: ƒ }
console.log(staff)             // ReferenceError: staff is not defined
/* Closure allows access to the staff variable */
elementary.getStaff()          // ["Seymour Skinner", "Edna Krabappel"]
elementary.addStaff("Otto Mann")
elementary.getStaff()          // ["Seymour Skinner", "Edna Krabappel", "Otto Mann"]

elementary被创建的时候,SpringfieldSchool已经返回。也就是说staff无法被外部访问。唯一可以访问的方式就是里面的闭包函数getStaffaddStaff

我们来看一个面试题:下面的代码有什么问题,如何修复?

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log(`The value ${arr[i]} is at index: ${i}`);
  }, (i+1) * 1000);
}

上面的代码输出的结果全部都一样:"The value undefined is at index: 4"。因为所有在setTimeout中定义的匿名函数都引用了同一个外部变量i。当匿名函数执行的时候,i的值为4。

这个问题可以改用IIFE(后面会介绍)方法来解决,通过对每一个匿名函数构建独立的外部作用域来实现。

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(`The value ${arr[j]} is at index: ${j}`);
    }, j * 1000);
  })(i)
}

当然,还有一个方法,使用let来声明i

const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log(`The value ${arr[i]} is at index: ${i}`);
  }, (i) * 1000);
}
立即调用的函数表达式(Immediate Invoked Function Expression)(IIFE)

一个IIFE是一个函数表达式在定义之后立即被调用。常用在你想对一个新声明的变量创建一个隔离的作用域。
它的格式为: (function(){....})()。前面的大括号用于告诉编译器这里不仅仅是函数定义,后面的大括号用于执行该函数。

var result = [];
for (var i=0; i < 5; i++) {
  result.push( function() { return i } );
}
console.log( result[1]() ); // 5
console.log( result[3]() ); // 5
result = [];
for (var i=0; i < 5; i++) {
  (function () {
    var j = i; // copy current value of i
    result.push( function() { return j } );
  })();
}
console.log( result[1]() ); // 1
console.log( result[3]() ); // 3

使用IIFE可以:

为函数绑定私有数据

创建一个新的环境

避免污染全局命名空间

环境(Context)

我们往往容易将环境(Context)和作用域(Scope)搞混,我来简单解释一下:

环境(Context): 由函数如何被调用而决定,往往指this

作用域(Scope): 可访问的变量。

函数调用:call, apply, bind

这三个方法都是为了将this绑定到函数,区别在于调用的方式。

.call()会立即执行函数,你需要把参数按顺序传入;

.apply()会立即执行函数,你需要把所有的参数组合为一个数组传入;

.call().apply()几乎相同。哪个传入参数方便,你就选择哪个。

const Snow = {surename: "Snow"}
const char = {
  surename: "Stark",
  knows: function(arg, name) {
    console.log(`You know ${arg}, ${name} ${this.surename}`);
  }
}
char.knows("something", "Bran");              // You know something, Bran Stark
char.knows.call(Snow, "nothing", "Jon");      // You know nothing, Jon Snow
char.knows.apply(Snow, ["nothing", "Jon"]);   // You know nothing, Jon Snow

注意:如果你将数组传入call函数,它会认为只有一个参数。

ES6允许使用新的操作符将数组变换为一个序列。

char.knows.call(Snow, ...["nothing", "Jon"]);  // You know nothing, Jon Snow

.bind()返回一个新的函数,以及相应的环境和参数。如果你想该函数稍后调用,那么推荐使用bind
.bind()函数的优点在于它可以记录一个执行环境,对于异步调用和事件驱动的编程很有用。

.bind()传参数的方式和call相同。

const Snow = {surename: "Snow"}
const char = {
  surename: "Stark",
  knows: function(arg, name) {
    console.log(`You know ${arg}, ${name} ${this.surename}`);}
  }
const whoKnowsNothing = char.knows.bind(Snow, "nothing");
whoKnowsNothing("Jon");  // You know nothing, Jon Snow
this关键字

要理解JavaScript中this关键字,特别是它指向谁,有时候相当地复杂。this的值通常由函数的执行环境决定。简单的说,执行环境指函数如何被调用的。this像是一个占位符(placeholder),它指向当方法被调用时,调用对应的方法的对象。

下面有序地列出了判断this指向的规则。如果第一条匹配,那么就不用去检查第二条了。

new绑定 - 当使用new关键字调用函数的时候,this指向新构建的对象。

function Person(name, age) {
  this.name = name;
  this.age =age;
  console.log(this);
}
const Rachel = new Person("Rachel", 30);   // { age: 30, name: "Rachel" }

显示绑定(Explicit binding) - 当使用call或则apply的时候,我们显示的传入一个对象参数,该参数会绑定到this。 注意:.bind()函数不一样。用bind定义一个新的函数,但是依然绑定到原来的对象。

function fn() {
  console.log(this);
}
var agent = {id: "007"};
fn.call(agent);    // { id: "007" }
fn.apply(agent);   // { id: "007" }
var boundFn = fn.bind(agent);
boundFn();         // { id: "007" }

隐式绑定 - 当一个函数在某个环境下调用(在某个对象里),this指向该对象。也就是说该函数是对象的一个方法。

var building = {
  floors: 5,
  printThis: function() {
    console.log(this);
  }
}
building.printThis();  // { floors: 5, printThis: function() {…} }

默认绑定 - 如果上面所有的规则都不满足,那么this指向全局对象(在浏览器中,就是window对象)。当函数没有绑定到某个对象,而多带带定义的时候,该函数默认绑定到全局对象。

function printWindow() {
  console.log(this)
}
printWindow();  // window object

注意:下面的情况中,inner函数中的this指向全局。

function Dinosaur(name) {
  this.name = name;
  var self = this;
  inner();
  function inner() {
    alert(this);        // window object — the function has overwritten the "this" context
    console.log(self);  // {name: "Dino"} — referencing the stored value from the outer context
  }
}
var myDinosaur = new Dinosaur("Dino");

词法(Lexical) this - 当是使用=>来定义函数时,this指向定义该函数时候外层的this。 备注:大概是和定义的词法(=>)有关,把它称作Lexical this

function Cat(name) {
  this.name = name;
  console.log(this);   // { name: "Garfield" }
  ( () => console.log(this) )();   // { name: "Garfield" }
}
var myCat = new Cat("Garfield");
严格(Strict)模式

如果你使用了"use strict"指令,那么JavaScript代码会在严格模式下执行。在严格模式下,对于词法分析和错误处理都有特定的规则。在这里我列出它的一些优点:

使得Debug更容易:以前会被忽略的错误现在会显示报错,比如赋值给一个不可写的全局变量或则属性;

避免不小心声明了全局变量:赋值给一个未定义的变量会报错;

避免无效使用delete:尝试去删除变量、函数或则不可删除的属性会抛出错误;

避免重复的属性名和参数值:对象上重复的属性和函数参数会抛出错误(在ES6中不再是这样);

使得eval()更加安全:在eval()中定义的变量和函数在外部作用域不可见;

“安全”的消除JavaScript中this的转换:如果this是null或则undefined不在转换到全局对象。也就是说在浏览器中使用this去指向全局对象不再可行。

对于在严格(strict)模式和测试阶段都没有发现的bug,不妨接入线上实时监控插件Fundebug

快速掌握JavaScript面试基础知识(一) -->

版权声明:
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/201...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107436.html

相关文章

  • 快速掌握JavaScript面试基础知识(三)

    摘要:第一部分请点击快速掌握面试基础知识一关键字如果使用关键字来调用函数式很特别的形式。该对象默认包含了指向原构造函数的属性。接下来通过例子来帮助理解属性包含了构造函数以及构造函数中在上定义的属性。也就是说,的回调函数后执行。 译者按: 总结了大量JavaScript基本知识点,很有用! 原文: The Definitive JavaScript Handbook for your next...

    qieangel2013 评论0 收藏0
  • 快速掌握JavaScript面试基础知识(一)

    摘要:根据调查,自年一来,是最流行的编程语言。在一个函数体中声明的变量和函数,周围的作用域内无法访问。也就是说被大括号包围起来的区域声明的变量外部将不可访问。一个常见的误解是使用声明的变量,其值不可更改。 译者按: 总结了大量JavaScript基本知识点,很有用! 原文: The Definitive JavaScript Handbook for your next developer ...

    acrazing 评论0 收藏0
  • 前端经典文章

    摘要:上周末看这篇文章时,偶有灵光,所以,分享出来给大家一起看看前端面试四月二十家前端面试题分享请各位读者添加一下作者的微信公众号,以后有新的文章,将在微信公众号直接推送给各位,非常感谢。 前端切图神器 avocode 有了这个神器,切图再也腰不酸,腿不疼了。 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,...

    lowett 评论0 收藏0
  • 前端相关大杂烩

    摘要:希望帮助更多的前端爱好者学习。前端开发者指南作者科迪林黎,由前端大师倾情赞助。翻译最佳实践译者张捷沪江前端开发工程师当你问起有关与时,老司机们首先就会告诉你其实是个没有网络请求功能的库。 前端基础面试题(JS部分) 前端基础面试题(JS部分) 学习 React.js 比你想象的要简单 原文地址:Learning React.js is easier than you think 原文作...

    fuyi501 评论0 收藏0
  • H5学习

    摘要:为此决定自研一个富文本编辑器。本文,主要介绍如何实现富文本编辑器,和解决一些不同浏览器和设备之间的。 对ES6Generator函数的理解 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。 JavaScript 设计模式 ② 巧用工厂模式和创建者模式 我为什么把他们两个放在一起讲?我觉得这两个设计模式有相似之处,有时候会一个设计模式不能满...

    aristark 评论0 收藏0

发表评论

0条评论

fyber

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<