[关闭]
@tsingwong 2019-05-13T15:23:24.000000Z 字数 17526 阅读 747

前端社招面试题

面试


防抖 节流

防抖是触发高频事件后,n 秒内函数只会执行一次,如果 n 秒内再次触发,重新计算时间。即高频事件在防抖的效果下,只会触发一次。思路:每次触发事件时都取消之前的延迟调用方法。

节流是触发高频事件后,n秒执行一次,即高频事件在节流的效果下,可能会触发多次。思路:每次触发事件时都会判断当前是否有等待执行的延迟函数。

  1. /**
  2. * 函数防抖
  3. * fn 需要防抖的高频函数
  4. * delay 延迟的秒数
  5. * immediate 是否立即执行
  6. */
  7. function debounce(fn, delay, immediate) {
  8. let timeout = null;
  9. return function (...args) {
  10. if (timeout) {
  11. clearTimeout(timeout);
  12. }
  13. if (immediate) {
  14. let callNow = !timeout;
  15. if (callNow) {
  16. fn.apply(this, args);
  17. }
  18. }
  19. timeout = setTimeout(() => {
  20. fn.apply(this, args);
  21. }, delay);
  22. }
  23. }
  1. /**
  2. * 函数节流
  3. * fn 需要节流的高频函数
  4. * delay 延迟的秒数
  5. */
  6. function throttle(fn, delay) {
  7. let timeout = null;
  8. return function (...args) {
  9. if (!timeout) {
  10. timeout = setTimeout(() => {
  11. fn.apply(this, args);
  12. timeout = null;
  13. }, delay);
  14. }
  15. }
  16. }

实现一个 new

  1. 创建一个全新空对象,空对象的 __proto__ 属性指向构造函数的原型对象;
  2. 空对象复制构造函数内的 this,用构造函数内部的方法修改空对象;
  3. 如果构造函数返回的是一个非基础类型的值,则返回这个值,否则返回上面创建的对象。
  1. function _new(fn, ...args) {
  2. const obj = Object.create(fn.prototype);
  3. const ret = fn.apply(obj, args);
  4. return ret instanceof Object ? ret : obj;
  5. }

setTimeout、Promise、async await 的顺序

JavaScript 主线程是一个执行栈(同步任务)和一个任务队列

JavaScript 中有宏观任务和微观任务之分。

微观任务优先级高优宏观任务。

每次 eventloop 中都会有一个 宏观任务,和多个微观任务。

执行顺序是先按照顺序执行微观任务,执行完成后执行宏观任务,依次执行下去,直到所有的任务都被执行完毕。

setTimeout 属于宏观任务。

宏观任务主要包含:

promise 和 async await 属于微观任务。

微观任务主要包含:

Promise 是同步的立即执行函数,主线程执行完毕后才会执行到 resolve 或 reject 函数。

Async 函数返回一个 Promise 对象,当函数执行时,遇到 await 先返回,等到触发异步操作完成时,再执行函数体后面的语句。可以理解为让出线程,跳出 async 函数体。

await 的含义为等待,也就是 async 函数需要等待 await 后的函数执行完成并且有了返回结果( Promise 对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。

执行完 await 后,回先让出线程,执行后面的同步代码。

  1. async function async1() {
  2. console.log('async1 start') // 4
  3. await async2() // 5 执行后让出线程,先执行同步代码
  4. console.log('async1 end') // 7 加入微观任务1 11 开始微观任务
  5. }
  6. async function async2() {
  7. console.log('async2') // 6
  8. }
  9. console.log('script start') // 1
  10. setTimeout(function () { // 2 加入宏观任务
  11. console.log('settimeout') // 13 宏观任务开始
  12. })
  13. async1() // 3
  14. new Promise(function (resolve) {
  15. console.log('promise1') // 8
  16. resolve() // 9 加入微观任务2
  17. }).then(function () {
  18. console.log('promise2') // 12 至此微观任务结束
  19. })
  20. console.log('script end') // 10 至此同步任务执行完毕
  21. // script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2-> settimeout

异步的历史

回调函数(callback)

  1. setTimeout(() => {
  2. // callback 函数体
  3. }, 1000)

缺点:回调地狱,不能用 try...catch 捕获错误,不能 return。

缺乏顺序性,嵌套函数过于耦合,嵌套函数过多时,很难处理错误。

Promise

链式调用,解决了 callback 回调地狱,每个 then 后面返回的都是一个新的 Promise。

缺点:无法取消 Promise,错误需要通过回调函数来捕获

  1. new Promise(function (resolve) {
  2. console.log('promise1') // 8
  3. resolve() // 9 加入微观任务2
  4. }).then(function () {
  5. console.log('promise2') // 12 至此微观任务结束
  6. })

Generator

可以控制函数执行。

  1. function *fetch() {
  2. yield ajax('XXX1', () => {})
  3. yield ajax('XXX2', () => {})
  4. yield ajax('XXX3', () => {})
  5. }
  6. let it = fetch()
  7. let result1 = it.next()
  8. let result2 = it.next()
  9. let result3 = it.next()

Async/Await

异步的终极解决方案

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

优点:代码清晰, 不用像 Promise 对象那样写一堆 then 链,处理了回调地狱的问题。

缺点:await 将异步代码改造成了同步代码,如果多个异步操作没有依赖性,反而会因为使用了 await 导致性能降低。

  1. async function test() {
  2. // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
  3. // 如果有依赖性的话,其实就是解决回调地狱的例子了
  4. await fetch('XXX1')
  5. await fetch('XXX2')
  6. await fetch('XXX3')
  7. }
  1. let a = 0
  2. let b = async () => {
  3. a = a + await 10
  4. console.log('2', a) // -> '2' 10
  5. }
  6. b()
  7. a++
  8. console.log('1', a) // -> '1' 1

上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。

实现 sleep函数

  1. // promise
  2. const sleepPromise = time => {
  3. return new Promise(resolve => setTimeout(resolve, time));
  4. };
  5. sleepPromise(1000).then(() => {
  6. console.log(1);
  7. });
  8. // Generator
  9. function* sleepGenerator(time) {
  10. yield new Promise(resolve => {
  11. setTimeout(resolve, time);
  12. });
  13. }
  14. sleepGenerator(1000).next().value.then(()=>{console.log(1)});
  15. // async/await
  16. async function output() {
  17. let out = await sleepPromise(1000);
  18. console.log(1);
  19. return out;
  20. }
  21. output();

浏览器是如何工作的?

  1. 浏览器首先使用 HTTP 或 HTTPS 协议,向服务端请求页面
  2. 将请求回来的 HTML 协议经过解析,构成 DOM 树
  3. 计算 DOM 树上的 CSS 属性
  4. 根据 CSS 属性对逐个元素进行渲染,得到内存的位图
  5. 对位图进行合成
  6. 合成后,在绘制到界面上

  7. 解析HTML,生成DOM树,解析CSS,生成CSSOM树

  8. 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
  9. Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
  10. Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
  11. Display:将像素发送给GPU,展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层,这里我们不展开,之后有机会会写一篇博客)

HTTP 状态码

1**:临时回应,表示客户端请继续

2**:请求成功

3**:请求目标有变化,304 资源未发生改变, 产生前提客户端本地已有缓存版本,并在 Request 中告诉服务器端,服务器端通过时间或 tag ,发现没更新的时候,返回一个不含 body 的 304 状态

4**:客户端请求错误

5**:服务端请求错误

HTTPS 是使用加密通道来传输 HTTP 的内容,但是 HTTPS 首先会跟服务端建立一条 TLS 加密通道,TLS 构建于 TCP 协议之上,实际上是对传输内容做一次加密,所以从传输内容来看,HTTPS 跟 HTTP 是没有区别的

重绘 重排 (Repaint Reflow)

重绘:元素几何属性发生变化,或样式发生变化而不影响布局的。outlinevisibilitycolorbackground-color等等。

重排:当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。如:增、删DOM节点,元素位置改变或应用动画,元素尺寸变更,浏览器窗口变化,读取某些元素属性:(offsetLeft/Top/Height/Width, clientTop/Left/Width/Height, scrollTop/Left/Width/Height, width/height, getComputedStyle(), currentStyle(IE) )

重排一定重绘,重绘不一定重排。

浏览器优化是在浏览器会将修改操作放在任务队列里,然后至少一个浏览器刷新时间(16.67ms)才会清空队列。需要注意的是,不要手动读取上面提到的属性,会强制渲染刷新队列。

减少重绘重排

  1. CSS
    1. 使用 transform 代替 top
    2. 避免 table 布局
    3. DOM 书的最末端改变 class
    4. 避免使用 CSS 表达式
    5. CSS3 硬件加速(GPU 加速),可以让transformopacityfilters这些动画不会引起回流重绘 。
  2. JavaScript
    1. 避免频繁操作样式,使用 style 属性一次性重写
    2. 避免频繁操作 DOM ,创建一个 documentFragment ,在上面完成 DOM 操作后,添加到文档中
    3. 避免频繁读取会引发重绘重拍的属性
    4. 对于复杂动画的元素使用绝对定位,使其脱离文档流

BFC

定位方案有三种:

BFC 是 Block Fromatting Contexts (块级格式上下文),属于普通流。

特性:

构建 BFC :

BFC 作用:

  1. 清除浮动:因为BFC可以包含浮动的元素,故把浮动元素的父元素设为BFC容器即可
  2. 解决元素间上下边距发生折叠的问题(外边距塌陷):把元素放入(注意:是放入,不是设置成)不同的BFC容器中
  3. 防止元素间的重叠覆盖,可实现自适应两列布局:左边元素左浮动,右边元素设置为BFC容器

浅拷贝与深拷贝

浅拷贝

  1. // ES6 之前
  2. function extendCopy(obj) {
  3. let temp = {};
  4. for (let i in obj) {
  5. temp[i] = obj[i];
  6. }
  7. return temp;
  8. }
  9. // ES6 之后两种方法 Object.assign() 和 `...`展开运算符
  10. let target = Object.assign({}, obj);
  11. let b = {...a}

深拷贝

JSON.parse(JSON.stringify(object)) 有一定局限性,会忽略 undefined、symbol,不能序列化函数,不能解决循环引用的对象。

  1. function deepClone(obj)
  2. // 是不是对象
  3. function isObject(o) {
  4. return (typeof o === 'object' || typeof o === 'function') && o !== null
  5. }
  6. if (!isObject(obj)) {
  7. throw new Error('非对象')
  8. }
  9. // 是不是数组
  10. let isArray = Array.isArray(obj)
  11. let newObj = isArray ? [...obj] : { ...obj }
  12. Reflect.ownKeys(newObj).forEach(key => {
  13. newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
  14. })
  15. return newObj
  16. }
  17. let obj = {
  18. a: [1, 2, 3],
  19. b: {
  20. c: 2,
  21. d: 3
  22. }
  23. }
  24. let newObj = deepClone(obj)
  25. newObj.b.c = 1
  26. console.log(obj.b.c) // 2

for……in for……of

for...in 语句用于遍历数组或者对象的属性(对数组或者对象的属性进行循环操作),遍历的是可枚举属性。一般需要配合 hasOwnProperty() 使用

for…of 语句是 ES6 新增的语法,多用于遍历可迭代对象( ArrayMapSetStringTypedArray,arguments 等等)

Event bus

不同组件之间通信:

Event Bus 发布订阅模式

  1. class EventEmeitter {
  2. constructor() {
  3. this._events = this.events || new Map();
  4. this._maxListeners = this._maxListeners || 10;
  5. }
  6. // 触发事件
  7. emit(type, ...args) {
  8. let handler;
  9. handler = this._events.get(type);
  10. handler.forEach(item => {
  11. if (args.length > 0) {
  12. item.apply(this, args);
  13. } else {
  14. item.call(this);
  15. }
  16. })
  17. return true;
  18. }
  19. // 监听 type 事件
  20. addListener(type, fn) {
  21. const handler = this._events.get(type);
  22. if (!handler) {
  23. this._events.set(type, [fn]);
  24. } else {
  25. handler.push(fn);
  26. }
  27. }
  28. removeListener(type, fn) {
  29. const handler = this._events.get(type);
  30. if (handler.indexOf(fn) > -1) {
  31. handler.splice(handler.indexOf(fn), 1);
  32. } else {
  33. consol.err('没找到方法');
  34. }
  35. }
  36. }
  37. // 实例化
  38. const emitter = new EventEmeitter();
  39. function expel (man) {
  40. console.log(`expel ${man}`);
  41. }
  42. function save (man) {
  43. console.log(`save ${man}`);
  44. }
  45. // 监听一个名为arson的事件对应一个回调函数
  46. emitter.addListener('arson', expel);
  47. emitter.addListener('arson', save);
  48. emitter.emit('arson', 'low-end');
  49. emitter.removeListener('arson', save);
  50. emitter.emit('arson', 'low-end');

前端路由

hash 路由: 带 # 表示,通过监听 URL 里的 hash 变化来进行路由跳转,兼容性较好。

  1. class Routers {
  2. constructor() {
  3. this.routes = {};
  4. this.currentUrl = '';
  5. this.refresh = this.refresh.bind(this);
  6. window.addEventListener('load', this.refresh, false);
  7. window.addEventListener('hashchange', this.refresh, false);
  8. }
  9. route(path, cb) {
  10. this.routes[path] = cb || function() {};
  11. }
  12. refresh() {
  13. this.currentUrl = location.hash.slice(1) || '/';
  14. this.routes[this.currentUrl]();
  15. }
  16. }
  17. window.Router = new Routers();
  18. var content = document.querySelector('body');
  19. // change Page anything
  20. function changeBgColor(color) {
  21. content.style.backgroundColor = color;
  22. }
  23. Router.route('/', function() {
  24. changeBgColor('yellow');
  25. });
  26. Router.route('/blue', function() {
  27. changeBgColor('blue');
  28. });
  29. Router.route('/green', function() {
  30. changeBgColor('green');
  31. });

history API

  1. class Routers {
  2. constructor() {
  3. this.routes = {};
  4. // 在初始化时监听popstate事件
  5. this._bindPopState();
  6. }
  7. // 初始化路由
  8. init(path) {
  9. history.replaceState({path: path}, null, path);
  10. this.routes[path] && this.routes[path]();
  11. }
  12. // 将路径和对应回调函数加入hashMap储存
  13. route(path, callback) {
  14. this.routes[path] = callback || function() {};
  15. }
  16. // 触发路由对应回调
  17. go(path) {
  18. history.pushState({path: path}, null, path);
  19. this.routes[path] && this.routes[path]();
  20. }
  21. // 监听popstate事件
  22. _bindPopState() {
  23. window.addEventListener('popstate', e => {
  24. const path = e.state && e.state.path;
  25. this.routes[path] && this.routes[path]();
  26. });
  27. }
  28. }

双向绑定

变化监测

拉:状态变化,框架会暴力对比DOM节点,得出某些需要重新渲染。Angular 是脏检查,React 使用的 虚拟DOM。

推:一定程度的状态变更,会通知到框架。

监听变化的方法:Object.defineProperty()Proxy(vue 3.0)中使用。

  1. function defineReactive(data, key, val) {
  2. Object.defineProperty(data, key, {
  3. enumerable: true,
  4. configurable: true,
  5. get() {
  6. return val;
  7. },
  8. set(newVal) {
  9. if (val === newVal) {
  10. return;
  11. }
  12. val = newVal;
  13. }
  14. })
  15. }

在 getter 中收集依赖,在 setter 中触发依赖

  1. // Dep.js
  2. export default class Dep {
  3. constructor() {
  4. this.subs = [];
  5. }
  6. addSub(sub) {
  7. this.subs.push(sub);
  8. }
  9. removeSub(sub) {
  10. let index = this.subs.indeOf(sub);
  11. if (index > -1) {
  12. this.subs.splice(sub, 1);
  13. }
  14. }
  15. // 增加依赖
  16. depend() {
  17. if(window.target) {
  18. this.addSub(window.target);
  19. }
  20. }
  21. // 通告,触发依赖
  22. notify() {
  23. // 不改变原数组
  24. const sub = this.sub.slice();
  25. sub.forEach(item => item.update());
  26. }
  27. }

调用

  1. function defineRective(data, key, val) {
  2. let dep = new Dep();
  3. Object.defineProperty(data, key, {
  4. enumerable: true,
  5. configurable: true,
  6. get() {
  7. dep.depend();
  8. return val;
  9. },
  10. set(newVal) {
  11. if (val === newVal) {
  12. return;
  13. }
  14. val = newVal;
  15. dep.notify();
  16. }
  17. });
  18. }

收集 依赖,依赖就是 Watcher

this

首先,new 的方式优先级最高,接下来是 bind 这些函数,然后是 obj.foo() 这种调用方式,最后是 foo 这种调用方式,同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。

注意:不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定

call apply bind

call 、apply 都是用于改变函数运行时的上下文而存在的,换句话说就是改变函数体内的 this 指向。

  1. // call 接受的是参数列表
  2. func.call(this, arg1, arg2);
  3. // apply 接受的是 数组
  4. func.apply(this, [arg1, arg2]);

call比apply的性能要好,平常可以多用call, call传入参数的格式正是内部所需要的格式

尤其是es6 引入了 Spread operator (延展操作符) 后,即使参数是数组,可以使用 call

  1. let params = [1,2,3,4]
  2. xx.call(obj, ...params)

bind() 方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

实现一个 bind 方法

  1. if (!Function.prototype.bind) {
  2. Function.prototype.bind = function (...args1) {
  3. var self = this, // 保存原函数
  4. context = [].shift.call(args1), // 保存需要绑定的this上下文,第一个参数时上下文
  5. args = [].slice.call(args1); // 剩余的参数转为数组
  6. return function (...args2) { // 返回一个新函数
  7. self.apply(context,[].concat.call(args1, [].slice.call(args2)));
  8. }
  9. }
  10. }

实现简易 Promise

可以把 Promise 看成一个状态机。初始是 pending 状态,可以通过函数 resolvereject ,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。

then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了。

对于 then 来说,本质上可以把它看成是 flatMap

  1. // 三种状态
  2. const PENDING = "pending";
  3. const RESOLVED = "resolved";
  4. const REJECTED = "rejected";
  5. // promise 接收一个函数参数,该函数会立即执行
  6. function MyPromise(fn) {
  7. let _this = this;
  8. _this.currentState = PENDING;
  9. _this.value = undefined;
  10. // 用于保存 then 中的回调,只有当 promise
  11. // 状态为 pending 时才会缓存,并且每个实例至多缓存一个
  12. _this.resolvedCallbacks = [];
  13. _this.rejectedCallbacks = [];
  14. _this.resolve = function (value) {
  15. if (value instanceof MyPromise) {
  16. // 如果 value 是个 Promise,递归执行
  17. return value.then(_this.resolve, _this.reject)
  18. }
  19. setTimeout(() => { // 异步执行,保证执行顺序
  20. if (_this.currentState === PENDING) {
  21. _this.currentState = RESOLVED;
  22. _this.value = value;
  23. _this.resolvedCallbacks.forEach(cb => cb());
  24. }
  25. })
  26. };
  27. _this.reject = function (reason) {
  28. setTimeout(() => { // 异步执行,保证执行顺序
  29. if (_this.currentState === PENDING) {
  30. _this.currentState = REJECTED;
  31. _this.value = reason;
  32. _this.rejectedCallbacks.forEach(cb => cb());
  33. }
  34. })
  35. }
  36. // 用于解决以下问题
  37. // new Promise(() => throw Error('error))
  38. try {
  39. fn(_this.resolve, _this.reject);
  40. } catch (e) {
  41. _this.reject(e);
  42. }
  43. }
  44. MyPromise.prototype.then = function (onResolved, onRejected) {
  45. var self = this;
  46. // 规范 2.2.7,then 必须返回一个新的 promise
  47. var promise2;
  48. // 规范 2.2.onResolved 和 onRejected 都为可选参数
  49. // 如果类型不是函数需要忽略,同时也实现了透传
  50. // Promise.resolve(4).then().then((value) => console.log(value))
  51. onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  52. onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;
  53. if (self.currentState === RESOLVED) {
  54. return (promise2 = new MyPromise(function (resolve, reject) {
  55. // 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
  56. // 所以用了 setTimeout 包裹下
  57. setTimeout(function () {
  58. try {
  59. var x = onResolved(self.value);
  60. resolutionProcedure(promise2, x, resolve, reject);
  61. } catch (reason) {
  62. reject(reason);
  63. }
  64. });
  65. }));
  66. }
  67. if (self.currentState === REJECTED) {
  68. return (promise2 = new MyPromise(function (resolve, reject) {
  69. setTimeout(function () {
  70. // 异步执行onRejected
  71. try {
  72. var x = onRejected(self.value);
  73. resolutionProcedure(promise2, x, resolve, reject);
  74. } catch (reason) {
  75. reject(reason);
  76. }
  77. });
  78. }));
  79. }
  80. if (self.currentState === PENDING) {
  81. return (promise2 = new MyPromise(function (resolve, reject) {
  82. self.resolvedCallbacks.push(function () {
  83. // 考虑到可能会有报错,所以使用 try/catch 包裹
  84. try {
  85. var x = onResolved(self.value);
  86. resolutionProcedure(promise2, x, resolve, reject);
  87. } catch (r) {
  88. reject(r);
  89. }
  90. });
  91. self.rejectedCallbacks.push(function () {
  92. try {
  93. var x = onRejected(self.value);
  94. resolutionProcedure(promise2, x, resolve, reject);
  95. } catch (r) {
  96. reject(r);
  97. }
  98. });
  99. }));
  100. }
  101. };
  102. // 规范 2.3
  103. function resolutionProcedure(promise2, x, resolve, reject) {
  104. // 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
  105. if (promise2 === x) {
  106. return reject(new TypeError("Error"));
  107. }
  108. // 规范 2.3.2
  109. // 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
  110. if (x instanceof MyPromise) {
  111. if (x.currentState === PENDING) {
  112. x.then(function (value) {
  113. // 再次调用该函数是为了确认 x resolve 的
  114. // 参数是什么类型,如果是基本类型就再次 resolve
  115. // 把值传给下个 then
  116. resolutionProcedure(promise2, value, resolve, reject);
  117. }, reject);
  118. } else {
  119. x.then(resolve, reject);
  120. }
  121. return;
  122. }
  123. // 规范 2.3.3.3.3
  124. // reject 或者 resolve 其中一个执行过得话,忽略其他的
  125. let called = false;
  126. // 规范 2.3.3,判断 x 是否为对象或者函数
  127. if (x !== null && (typeof x === "object" || typeof x === "function")) {
  128. // 规范 2.3.3.2,如果不能取出 then,就 reject
  129. try {
  130. // 规范 2.3.3.1
  131. let then = x.then;
  132. // 如果 then 是函数,调用 x.then
  133. if (typeof then === "function") {
  134. // 规范 2.3.3.3
  135. then.call(
  136. x,
  137. y => {
  138. if (called) return;
  139. called = true;
  140. // 规范 2.3.3.3.1
  141. resolutionProcedure(promise2, y, resolve, reject);
  142. },
  143. e => {
  144. if (called) return;
  145. called = true;
  146. reject(e);
  147. }
  148. );
  149. } else {
  150. // 规范 2.3.3.4
  151. resolve(x);
  152. }
  153. } catch (e) {
  154. if (called) return;
  155. called = true;
  156. reject(e);
  157. }
  158. } else {
  159. // 规范 2.3.4,x 为基本类型
  160. resolve(x);
  161. }
  162. }

typeof VS instanceof

JavaScript 有 7 种语言类型

7种语言类型中,除了 Object 其他都属于 基本类型,基本类型保存在栈内存上。

Object 类型Array 类型Date 类型RegExp 类型Function 类型 等属于引用类型,引用类型保存在堆内存上。

typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。

instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型。

  1. const Person = function() {}
  2. const p1 = new Person()
  3. p1 instanceof Person // true
  4. var str = 'hello world'
  5. str instanceof String // false
  6. var str1 = new String('hello world')
  7. str1 instanceof String // true

toString 是 Object 原型对象上的方法,使用 call 来调用该方法会返回调用者的类型字符串,格式为 [object,xxx],xxx 是调用者的数据类型,包括:String、Number、Boolean、Undefined、Null、Function、Date、Array、RegExp、Error、HTMLDocument 等, 基本上,所有的数据类型都可以通过这个方法获取到。

  1. Object.prototype.toString.call(XXX)

闭包

定义:函数 A 内部包含函数 B,函数 B 可以访问到 函数 A 的变量,那么 函数B 就是闭包。

闭包的意义,就是让我们可以间接访问函数内部的变量。

例如

  1. for(var i = 1; i <= 5; i++) {
  2. setTimeout(() => {
  3. console.log(i)
  4. }, i * 1000);
  5. }

返回结果是 11 11 11 11 11

修改上面代码使其符合预期:

  1. // 块级作用域
  2. // 1.1
  3. for(let i = 0; i <= 5; i++) {
  4. setTimeout(() => {
  5. console.log(i);
  6. }, i * 1000)
  7. }
  8. // 1.2
  9. for (var i = 0; i <= 5; i++) {
  10. let _i = i;
  11. setTimeout(() => {
  12. cosole.log(_i);
  13. }, _i * 1000)
  14. }
  15. // 闭包
  16. // 2.1
  17. for(var i = 0; i <= 5; i++) {
  18. ;(i => {
  19. setTimeout(() => {
  20. console.log(i);
  21. }, i *1000);
  22. })(i);
  23. }
  24. // 3 setTimeout 函数的第三的参数,会作为回调函数的第一个参数传入
  25. // 3.1
  26. for (var i = 0; i < 5; i++) {
  27. setTimeout(console.log, i * 1000, i)
  28. }
  29. // 3.2
  30. for (var i = 0; i <= 5; i++) {
  31. setTimeout(console.log.bind(Object.create(null), i), i * 1000)
  32. }

实现自增函数

  1. let count = (function () {
  2. let i = 1;
  3. return function () {
  4. return i++;
  5. };
  6. })();

无重复字符的最长子串

  1. //Q:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

JavaScript 中的继承

原型链继承

  1. function Parent () {
  2. this.name = 'kevin';
  3. }
  4. Parent.prototype.getName = function () {
  5. console.log(this.name);
  6. }
  7. function Child () {
  8. }
  9. Child.prototype = new Parent();
  10. var child1 = new Child();
  11. console.log(child1.getName()) // kevin

缺点:

  1. 引用类型的属性被所有实例共享
  2. 在创建子类型的实例时,不能向超类型的构造函数中传递参数。

构造函数继承

  1. function Parent () {
  2. this.names = ['kevin', 'daisy'];
  3. }
  4. function Child () {
  5. Parent.call(this);
  6. }
  7. var child1 = new Child();
  8. child1.names.push('yayu');
  9. console.log(child1.names); // ["kevin", "daisy", "yayu"]
  10. var child2 = new Child();
  11. console.log(child2.names); // ["kevin", "daisy"]

优点:即上面的缺点。

缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法。

组合继承

  1. function Parent (name) {
  2. this.name = name;
  3. this.colors = ['red', 'blue', 'green'];
  4. }
  5. Parent.prototype.getName = function () {
  6. console.log(this.name)
  7. }
  8. function Child (name, age) {
  9. Parent.call(this, name);
  10. this.age = age;
  11. }
  12. Child.prototype = new Parent();
  13. Child.prototype.constructor = Child;
  14. var child1 = new Child('kevin', '18');
  15. child1.colors.push('black');
  16. console.log(child1.name); // kevin
  17. console.log(child1.age); // 18
  18. console.log(child1.colors); // ["red", "blue", "green", "black"]
  19. var child2 = new Child('daisy', '20');
  20. console.log(child2.name); // daisy
  21. console.log(child2.age); // 20
  22. console.log(child2.colors); // ["red", "blue", "green"]

原型式继承

  1. function createObj(o) {
  2. function F(){}
  3. F.prototype = o;
  4. return new F();
  5. }

是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

缺点:

包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

ES6 Class 继承

  1. class Parent {
  2. constructor(name) {
  3. this.name = name;
  4. this.colors = ['red', 'blue', 'green'];
  5. }
  6. getName() {
  7. console.log(this.name);
  8. }
  9. }
  10. class Child extends Parent {
  11. constructor(name, age) {
  12. super();
  13. this.age = age;
  14. }
  15. getAge() {
  16. console.log(this.age);
  17. }
  18. }
  19. var child1 = new Child('kevin', '18');
  20. child1.colors.push('black');
  21. console.log(child1.name); // kevin
  22. console.log(child1.age); // 18
  23. console.log(child1.colors); // ["red", "blue", "green", "black"]
  24. var child2 = new Child('daisy', '20');
  25. console.log(child2.name); // daisy
  26. console.log(child2.age); // 20
  27. console.log(child2.colors); // ["red", "blue", "green"]
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注