译者按:
总结了大量JavaScript基本知识点,很有用!
为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。
根据StackOverflow调查, 自2014年一来,JavaScript是最流行的编程语言。当然,这也在情理之中,毕竟1/3的开发工作都需要一些JavaScript知识。因此,如果你希望在成为一个开发者,你应该学会这门语言。
这篇博客的主要目的是将所有面试中常见的概念总结,方便你快速去了解。(鉴于本文内容过长,方便阅读,将分为三篇博客来翻译, 此为第三部分。第一部分请点击快速掌握JavaScript面试基础知识(一))
new关键字
如果使用
new
关键字来调用函数式很特别的形式。我们把那些用
new
调用的函数叫做构造函数(constructor function)。
使用了
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); let realBird= new Bird();console .log(realBird)
为了便于对比理解,译者额外增加了测试了一种情况:
function MBird
( ) { this .wings =2 ; return "hello" ; } let realMBrid = new MBird();console .log(realMBird)
你会发现,这一句
return "hello"
并没有生效!
原型和继承
原型(Prototype)是JavaScript中最容易搞混的概念,其中一个原因是
prototype
可以用在两个不同的情形下。
原型关系
每一个对象都有一个
prototype
对象,里面包含了所有它的原型的属性。
.__proto__
是一个不正规的机制(ES6中提供),用来获取一个对象的prototype。你可以理解为它指向对象的
parent
。
所有普通的对象都继承
.constructor
属性,它指向该对象的构造函数。当一个对象通过构造函数实现的时候,
__proto__
属性指向构造函数的构造函数的
.prototype
。
Object.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' );console
.log(Dog.prototype) console .log(rusty) console .log(rusty.describe()) console .log(rusty.__proto__) console .log(rusty.constructor)
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' ; console .log('airbags' in myCar) console .log(myCar.wheels) console .log(myCar.year) console .log(myCar.hasOwnProperty('airbags' )) console .log(myCar.hasOwnProperty('color' ))
Object.create(obj)
创建一个新的对象,prototype指向
obj
。
var
dog = { legs : 4 };var myDog = Object .create(dog);console .log(myDog.hasOwnProperty('legs' )) console .log(myDog.legs) console .log(myDog.__proto__ === dog)
继承是引用传值
继承属性都是通过引用的形式。我们通过例子来形象理解:
var objProt = { text : 'original' };var objAttachedToProt = Object .create(objProt);console .log(objAttachedToProt.text) objProt.text = 'prototype property changed' ; console .log(objAttachedToProt.text) objProt = { text : 'replacing property' }; console .log(objAttachedToProt.text)
经典继承 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 ; callback(greeting); } greetingAsync("fundebug" , console .log); console .log("start greeting" );
我们在
greetingAsync
中构造了
greeting
语句,然后通过
callback
,让用户自己去定义greeting的具体方式。为方便起见,我们时候直接使用
console.log
。
上面代码执行首先会打印
start greeting
,然后才是
hello, fundebug
。也就是说,
greetingAsync
的回调函数后执行。在网站开发中,和服务器交互的时候需要不断地发送各种请求,而一个页面可能有几十个请求。如果我们一个一个按照顺序来请求并等待结果,串行的执行会使得网页加载很慢。通过异步的方式,我们可以先发请求,然后在回调中处理请求结果,高效低并发处理。