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

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

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

根据StackOverflow调查, 自2014年一来,JavaScript是最流行的编程语言。固然,这也在情理之中,毕竟1/3的开发工做都须要一些JavaScript知识。所以,若是你但愿在成为一个开发者,你应该学会这门语言。面试

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

new关键字

若是使用new关键字来调用函数式很特别的形式。咱们把那些用new调用的函数叫作构造函数(constructor function)。浏览器

使用了new的函数到底作了什么事情呢?服务器

  • 建立一个新的对象
  • 将对象的prototype设置为构造函数的prototype
  • 执行构造函数,this执行新构造的对象
  • 返回该对象。若是构造函数返回对象,那么返回该构造对象。
// 为了更好地理解底层,咱们来定义new关键字
function myNew(constructor, ...arguments) {
  var obj = {}
  Object.setPrototypeOf(obj, constructor.prototype);
  return constructor.apply(obj, arguments) || obj
}
复制代码

使用new和不使用的区别在哪里呢?并发

function Bird() {
  this.wings = 2;
}
/* 普通的函数调用 */
let fakeBird = Bird();
console.log(fakeBird);    // undefined
/* 使用new调用 */
let realBird= new Bird();
console.log(realBird)     // { wings: 2 }
复制代码

为了便于对比理解,译者额外增长了测试了一种状况:app

function MBird(){
  this.wings =2; 
  return "hello";
}

let realMBrid = new MBird();
console.log(realMBird) // { wings: 2 }
复制代码

你会发现,这一句return "hello"并无生效!异步

原型和继承

原型(Prototype)是JavaScript中最容易搞混的概念,其中一个缘由是prototype能够用在两个不一样的情形下。编程语言

  • 原型关系 每个对象都有一个prototype对象,里面包含了全部它的原型的属性。 .__proto__是一个不正规的机制(ES6中提供),用来获取一个对象的prototype。你能够理解为它指向对象的parent。 全部普通的对象都继承.constructor属性,它指向该对象的构造函数。当一个对象经过构造函数实现的时候,__proto__属性指向构造函数的构造函数的.prototypeObject.getPrototypeOf()是ES5的标准函数,用来获取一个对象的原型。

  • 原型属性 每个函数都有一个.prototype属性,它包含了全部能够被继承的属性。该对象默认包含了指向原构造函数的.constructor属性。每个使用构造函数建立的对象都有一个构造函数属性。

接下来经过例子来帮助理解:

function Dog(breed, name){
  this.breed = breed,
  this.name = name
}
Dog.prototype.describe = function() {
  console.log(`${this.name} is a ${this.breed}`)
}
const rusty = new Dog('Beagle', 'Rusty');

/* .prototype 属性包含了构造函数以及构造函数中在prototype上定义的属性。*/
console.log(Dog.prototype)  // { describe: ƒ , constructor: ƒ }

/* 使用Dog构造函数构造的对象 */
console.log(rusty)   // { breed: "Beagle", name: "Rusty" }
/* 从构造函数的原型中继承下来的属性或函数 */
console.log(rusty.describe())   // "Rusty is a Beagle"
/* .__proto__ 属性指向构造函数的.prototype属性 */
console.log(rusty.__proto__)    // { describe: ƒ , constructor: ƒ }
/* .constructor 属性指向构造函数 */
console.log(rusty.constructor)  // ƒ Dog(breed, name) { ... }
复制代码

JavaScript的使用能够说至关灵活,为了不出bug了不知道,不妨接入Fundebug线上实时监控

原型链

原型链是指对象之间经过prototype连接起来,造成一个有向的链条。当访问一个对象的某个属性的时候,JavaScript引擎会首先查看该对象是否包含该属性。若是没有,就去查找对象的prototype中是否包含。以此类推,直到找到该属性或则找到最后一个对象。最后一个对象的prototype默认为null。

拥有 vs 继承

一个对象有两种属性,分别是它自身定义的和继承的。

function Car() { }
Car.prototype.wheels = 4;
Car.prototype.airbags = 1;

var myCar = new Car();
myCar.color = 'black';

/* 原型链中的属性也能够经过in来查看: */
console.log('airbags' in myCar)  // true
console.log(myCar.wheels)        // 4
console.log(myCar.year)          // undefined

/* 经过hasOwnProperty来查看是否拥有该属性: */
console.log(myCar.hasOwnProperty('airbags'))  // false — Inherited
console.log(myCar.hasOwnProperty('color'))    // true
复制代码

Object.create(obj) 建立一个新的对象,prototype指向obj

var dog = { legs: 4 };
var myDog = Object.create(dog);

console.log(myDog.hasOwnProperty('legs'))  // false
console.log(myDog.legs)                    // 4
console.log(myDog.__proto__ === dog)       // true
复制代码

继承是引用传值

继承属性都是经过引用的形式。咱们经过例子来形象理解:

var objProt = { text: 'original' };
var objAttachedToProt = Object.create(objProt);
console.log(objAttachedToProt.text)   // original

// 咱们更改objProt的text属性,objAttachedToProt的text属性一样更改了
objProt.text = 'prototype property changed';
console.log(objAttachedToProt.text)   // prototype property changed

// 可是若是咱们讲一个新的对象赋值给objProt,那么objAttachedToProt的text属性不受影响
objProt = { text: 'replacing property' };
console.log(objAttachedToProt.text)   // prototype property changed
复制代码

经典继承 vs 原型继承

Eric Elliott的文章有很是详细的介绍:Master the JavaScript Interview: What’s the Difference Between Class & Prototypal Inheritance? 做者认为原型继承是优于经典的继承的,并提供了一个视频介绍:https://www.youtube.com/watch?v=wfMtDGfHWpA&feature=youtu.be

异步JavaScript

JavaScript是一个单线程程序语言,也就是说JavaScript引擎一次只能执行某一段代码。它致使的问题就是:若是有一段代码须要耗费很长的时间执行,其它的操做就被卡住了。JavaScript使用Call Stack来记录函数的调用。一个Call Stack能够当作是一摞书。最后一本书放在最上面,也最早被移走。最早放的书在最底层,最后被移走。

为了不复杂代码占用CPU太长时间,一个解法就是定义异步回调函数。咱们本身来定义一个异步函数看看:

function greetingAsync(name, callback){
  let greeting = "hello, " + name ;
  setTimeout(_ => callback(greeting),0);
}

greetingAsync("fundebug", console.log);
console.log("start greeting");
复制代码

咱们在greetingAsync中构造了greeting语句,而后经过setTimeout定义了异步,callback函数,是为了让用户本身去定义greeting的具体方式。为方便起见,咱们时候直接使用console.log。 上面代码执行首先会打印start greeting,而后才是hello, fundebug。也就是说,greetingAsync的回调函数后执行。在网站开发中,和服务器交互的时候须要不断地发送各类请求,而一个页面可能有几十个请求。若是咱们一个一个按照顺序来请求并等待结果,串行的执行会使得网页加载很慢。经过异步的方式,咱们能够先发请求,而后在回调中处理请求结果,高效低并发处理。

下面经过一个例子来描述整个执行过程:

const first = function () {
  console.log('First message')
}
const second = function () {
  console.log('Second message')
}
const third = function() {
  console.log('Third message')
}

first();
setTimeout(second, 0);
third();

// 输出:
  // First message
  // Third message
  // Second message
复制代码
  1. 初始状态下,浏览器控制台没有输出,而且事件管理器(Event Manager)是空的;
  2. first()被添加到调用栈
  3. console.log("First message")加到调用栈
  4. console.log("First message")执行并输出“First message”到控制台
  5. console.log("First message")从调用栈中移除
  6. first()从调用栈中移除
  7. setTimeout(second, 0)加到调用栈
  8. setTimeout(second, 0)执行,0ms以后,second()被加到回调队列
  9. setTimeout(second, 0)从调用栈中移除
  10. third()加到调用栈
  11. console.log("Third message")加到调用栈
  12. console.log("Third message")执行并输出“Third message”到控制台
  13. console.log("Third message")从调用栈中移除
  14. third()从调用栈中移除
  15. Event Loop 将second()从回调队列移到调用栈
  16. console.log("Second message")加到调用栈
  17. console.log("Second message")Second message”到控制台
  18. console.log("Second message")从调用栈中移除
  19. Second()从调用栈中移除

特别注意的是:second()函数在0ms以后并无当即执行,你传入到setTimeout()函数的时间和second()延迟执行的时间并不必定直接相关。事件管理器等到setTimeout()设置的时间到期才会将其加入回调队列,而回调队列中它执行的时间和它在队列中的位置已经它前面的函数的执行时间有关。

更多

相关文章
相关标签/搜索