转载于https://github.com/poetries/FE-Interview-Questions,by poetriesjavascript
当执行
JS
代码时,会生成执行环境,只要代码不是写在函数中的,就是在全局执行环境中,函数中的代码会产生函数执行环境,只此两种执行环境。css
b() // call b console.log(a) // undefined var a = 'Hello world' function b() { console.log('call b') }
想必以上的输出你们确定都已经明白了,这是由于函数和变量提高的缘由。一般提高的解释是说将声明的代码移动到了顶部,这其实没有什么错误,便于你们理解。可是更准确的解释应该是:在生成执行环境时,会有两个阶段。第一个阶段是建立的阶段,
JS
解释器会找出须要提高的变量和函数,而且给他们提早在内存中开辟好空间,函数的话会将整个函数存入内存中,变量只声明而且赋值为undefined
,因此在第二个阶段,也就是代码执行阶段,咱们能够直接提早使用html
b() // call b second function b() { console.log('call b fist') } function b() { console.log('call b second') } var b = 'Hello world'
var
会产生不少错误,因此在 ES6中引入了let
。let
不能在声明前使用,可是这并非常说的let
不会提高,let
提高了,在第一阶段内存也已经为他开辟好了空间,可是由于这个声明的特性致使了并不能在声明前使用前端
call
和 apply
都是为了解决改变 this
的指向。做用都是相同的,只是传参的方式不一样。call
能够接收一个参数列表,apply
只接受一个参数数组let a = { value: 1 } function getValue(name, age) { console.log(name) console.log(age) console.log(this.value) } getValue.call(a, 'yck', '24') getValue.apply(a, ['yck', '24'])
bind
和其余两个方法做用也是一致的,只是该方法会返回一个函数。而且咱们能够经过bind
实现柯里化java
对于实现如下几个函数,能够从几个方面思考node
window
this
指向,让新的对象能够执行该函数。那么思路是否能够变成给新的对象添加一个函数,而后在执行完之后删除?Function.prototype.myBind = function (context) { if (typeof this !== 'function') { throw new TypeError('Error') } var _this = this var args = [...arguments].slice(1) // 返回一个函数 return function F() { // 由于返回了一个函数,咱们能够 new F(),因此须要判断 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, args.concat(...arguments)) } }
Function.prototype.myCall = function (context) { var context = context || window // 给 context 添加一个属性 // getValue.call(a, 'yck', '24') => a.fn = getValue context.fn = this // 将 context 后面的参数取出来 var args = [...arguments].slice(1) // getValue.call(a, 'yck', '24') => a.fn('yck', '24') var result = context.fn(...args) // 删除 fn delete context.fn return result }
Function.prototype.myApply = function (context) { var context = context || window context.fn = this var result // 须要判断是否存储第二个参数 // 若是存在,就将第二个参数展开 if (arguments[1]) { result = context.fn(...arguments[1]) } else { result = context.fn() } delete context.fn return result }
prototype
属性,除了 Function.prototype.bind()
,该属性指向原型。__proto__
属性,指向了建立该对象的构造函数的原型。其实这个属性指向了 [[prototype]]
,可是 [[prototype]]
是内部属性,咱们并不能访问到,因此使用 _proto_
来访问。__proto__
来寻找不属于该对象的属性,__proto__
将对象链接起来组成了原型链。Object.prototype.toString.call(xx)
。这样咱们就能够得到相似 [object Type]
的字符串。instanceof
能够正确的判断对象的类型,由于内部机制是经过判断对象的原型链中是否是能找到类型的 prototype
function a() { return () => { return () => { console.log(this) } } } console.log(a()()())
箭头函数实际上是没有
this
的,这个函数中的this
只取决于他外面的第一个不是箭头函数的函数的this
。在这个例子中,由于调用a
符合前面代码中的第一个状况,因此this
是window
。而且this
一旦绑定了上下文,就不会被任何代码改变webpack
function foo() { console.log(this.a) } var a = 1 foo() var obj = { a: 2, foo: foo } obj.foo() // 以上二者状况 `this` 只依赖于调用函数前的对象,优先级是第二个状况大于第一个状况 // 如下状况是优先级最高的,`this` 只会绑定在 `c` 上,不会被任何方式修改 `this` 指向 var c = new foo() c.a = 3 console.log(c.a) // 还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new
async
和await
相比直接使用Promise
来讲,优点在于处理 then 的调用链,可以更清晰准确的写出代码。缺点在于滥用await
可能会致使性能问题,由于await
会阻塞代码,也许以后的异步代码并不依赖于前者,但仍然须要等待前者完成,致使代码失去了并发性git
下面来看一个使用 await
的代码。github
var a = 0 var b = async () => { a = a + await 10 console.log('2', a) // -> '2' 10 a = (await 10) + a console.log('3', a) // -> '3' 20 } b() a++ console.log('1', a) // -> '1' 1
b
先执行,在执行到 await 10
以前变量 a
仍是 0
,由于在 await
内部实现了 generators
,generators
会保留堆栈中东西,因此这时候 a = 0
被保存了下来await
是异步操做,遇到await
就会当即返回一个pending
状态的Promise
对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,因此会先执行 console.log('1', a)
a = 10
Generator
是ES6
中新增的语法,和Promise
同样,均可以用来异步编程web
// 使用 * 表示这是一个 Generator 函数 // 内部能够经过 yield 暂停代码 // 经过调用 next 恢复执行 function* test() { let a = 1 + 2; yield 2; yield 3; } let b = test(); console.log(b.next()); // > { value: 2, done: false } console.log(b.next()); // > { value: 3, done: false } console.log(b.next()); // > { value: undefined, done: true }
从以上代码能够发现,加上
*
的函数执行后拥有了next
函数,也就是说函数执行后返回了一个对象。每次调用next
函数能够继续执行被暂停的代码。如下是Generator
函数的简单实现
// cb 也就是编译过的 test 函数 function generator(cb) { return (function() { var object = { next: 0, stop: function() {} }; return { next: function() { var ret = cb(object); if (ret === undefined) return { value: undefined, done: true }; return { value: ret, done: false }; } }; })(); } // 若是你使用 babel 编译后能够发现 test 函数变成了这样 function test() { var a; return generator(function(_context) { while (1) { switch ((_context.prev = _context.next)) { // 能够发现经过 yield 将代码分割成几块 // 每次执行 next 函数就执行一块代码 // 而且代表下次须要执行哪块代码 case 0: a = 1 + 2; _context.next = 4; return 2; case 4: _context.next = 6; return 3; // 执行完毕 case 6: case "end": return _context.stop(); } } }); }
Promise
是 ES6
新增的语法,解决了回调地狱的问题。Promise
当作一个状态机。初始是 pending
状态,能够经过函数 resolve
和 reject
,将状态转变为 resolved
或者 rejected
状态,状态一旦改变就不能再次变化。then
函数会返回一个 Promise
实例,而且该返回值是一个新的实例而不是以前的实例。由于 Promise
规范规定除了 pending
状态,其余状态是不能够改变的,若是返回的是一个相同实例的话,多个 then
调用就失去意义了。 对于 then
来讲,本质上能够把它当作是 flatMap
// 三种状态 const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; // promise 接收一个函数参数,该函数会当即执行 function MyPromise(fn) { let _this = this; _this.currentState = PENDING; _this.value = undefined; // 用于保存 then 中的回调,只有当 promise // 状态为 pending 时才会缓存,而且每一个实例至多缓存一个 _this.resolvedCallbacks = []; _this.rejectedCallbacks = []; _this.resolve = function (value) { if (value instanceof MyPromise) { // 若是 value 是个 Promise,递归执行 return value.then(_this.resolve, _this.reject) } setTimeout(() => { // 异步执行,保证执行顺序 if (_this.currentState === PENDING) { _this.currentState = RESOLVED; _this.value = value; _this.resolvedCallbacks.forEach(cb => cb()); } }) }; _this.reject = function (reason) { setTimeout(() => { // 异步执行,保证执行顺序 if (_this.currentState === PENDING) { _this.currentState = REJECTED; _this.value = reason; _this.rejectedCallbacks.forEach(cb => cb()); } }) } // 用于解决如下问题 // new Promise(() => throw Error('error)) try { fn(_this.resolve, _this.reject); } catch (e) { _this.reject(e); } } MyPromise.prototype.then = function (onResolved, onRejected) { var self = this; // 规范 2.2.7,then 必须返回一个新的 promise var promise2; // 规范 2.2.onResolved 和 onRejected 都为可选参数 // 若是类型不是函数须要忽略,同时也实现了透传 // Promise.resolve(4).then().then((value) => console.log(value)) onResolved = typeof onResolved === 'function' ? onResolved : v => v; onRejected = typeof onRejected === 'function' ? onRejected : r => throw r; if (self.currentState === RESOLVED) { return (promise2 = new MyPromise(function (resolve, reject) { // 规范 2.2.4,保证 onFulfilled,onRjected 异步执行 // 因此用了 setTimeout 包裹下 setTimeout(function () { try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject);