JavaScript interview memo
Posted 烟火
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript interview memo相关的知识,希望对你有一定的参考价值。
1、this 的指向
1)、由 new 调用?绑定到新创建的对象。
2)、 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
3)、 由上下文对象调用?绑定到那个上下文对象。
4)、 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
2、new关键字的过程
- 创建一个新对象
- 将 this 绑定到新创建的对象上
- 在新创建的对象上添加一个叫 __proto__ 的属性,指向构造函数的原型 prototype 对象
- 返回新创建的对象 return this
function Student(name, age) { this.name = name; this.age = age; } var first = new Student(\'John\', 26);
-
创建了一个新对象 —— first 对象
-
this 绑定到 first 对象。所有 this 引用指向 first。
-
添加__proto__。first.__proto__ 现在指向 Studnet.prototype。
-
所有事情完成后,我们将新的 first 对象返回出来赋值给新的 first 变量。
3、call、apply 和 bind 的区别
共同点:都是改变代码运行时的上下文
不同点:apply 接收一个包含多个参数的数组,而 call 与 bind 接收的是若干个参数的列表,另外 bind 会创建一个被初始化参数改造的新函数
// 一个简单的实现 Function.prototype.bind = function(ctx) { var fn = this; return function() { fn.apply(ctx, arguments); } // ...... }
4、获取对象属性的方法
Object.getOwnPropertyNames vs Object.keys
var a = {}; Object.defineProperties(a, { one: {enumerable: true, value: \'one\'}, two: {enumerable: false, value: \'two\'}, }); // 只能获取 enumerable 为 true 即可枚举的属性 // ["one"] Object.keys(a); // 可不可枚举都能得到 // ["one", "two"] Object.getOwnPropertyNames(a);
5、闭包
定义:是一个由函数和其词法作用域绑定在一起所形成的组合结构
特征:函数可以记住并访问其词法作用域,即时函数在当前的词法作用域外执行也可以
栗子:
function Eat() { var desc = \' is eating\'; function eat(animal) { console.log(animal.name + desc); } return eat; } var eat = Eat(); //全局变量 var desc = \'正在吃...\'; var dog = {name: \'dog\'}; // 闭包里的词法作用域(又叫静态作用域)是在函数创建时就和函数绑定了 // 知道输出结果了吧! eat(dog); // 还不明白,看这个 var x = 1; function foo() { console.log(x); } function bar(fn) { var x = 2; fn(); } //输出? bar(foo);
应用:模块加载器
var MyModules = (function Manager(){ var modules = {}; function define(name, deps, impl) { for(var i=0; i<deps.length; i++) { deps[i] = modules[deps[i]]; } modules[name] = impl.apply( impl, deps ); } function get(name) { return modules[name]; } return { define: define, get: get }; })();
6、怎么理解JS模块化?有没有使用过webpack?
优点:提高代码的可维护性、可重用性,避免污染命名空间
7、怎么弥补 CSS in JS 不支持嵌套、keyframes、媒体查询等特性的缺陷 ?
使用 styled-components,它的实现原理是使用 ES6的模板字符串特性,解决了在 JSX 中编写 CSS 的各种问题,由于它最终是在 head 里插入 style,所以它支持所有 CSS 特性,同时又能获取 JS 强大的控制力,具体详情戳这里styled-components 背后的魔法
8、requestAnimationFrame 优化原理
requestAnimationFrame 会自动匹配 W3C 所建议的刷新频率,既不会因为频率太高,增加开销,也不会频率太低,造成动画丢帧卡顿。
优点:
- requestAnimationFrame 会把每一帧中所有的 DOM 操作使用 DocumentFragment 集中起来,在一次重绘或回流中完成
- 在隐藏或不可见的元素中,
requestAnimationFrame
将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量 - 如果页面不是激活状态下的话,会暂停调用来提升性能和电池寿命。
9、怎么突破 localStorage 的容量限制
- localstorage的跨域存储方案,使用postMessage
- IndexedDB 用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对该数据的高性能搜索。
10、Event Loop 原理
当 js 代码执行时会将不同的变量存到堆(heap)和栈(stack)中。其中,堆中存放着一些对象,而栈中则存放着一些基础类型变量以及对象的指针。
而执行栈是指当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方。这个地方被称为执行栈。
当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境,这个过程反复进行,直到执行栈中的代码全部执行完毕。
当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
这个过程需记住当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
以下事件属于宏任务:
setInterval()
setTimeout()
以下事件属于微任务
new Promise()
new MutaionObserver()
那么,下面这段代码的结果是 ?
1 setTimeout(function () { 2 console.log(1); 3 }); 4 5 new Promise(function(resolve,reject){ 6 console.log(2) 7 resolve(3) 8 }).then(function(val){ 9 console.log(val); 10 })
// 2 3 1
详细规则为:
当一个程序有:setTimeout, setInterval ,setImmediate, I/O, UI渲染,Promise ,process.nextTick, Object.observe, MutationObserver的时候:
1.先执行 macrotasks:I/O -》 UI渲染-》requestAnimationFrame
2.再执行 microtasks :process.nextTick -》 Promise -》MutationObserver ->Object.observe
3.再把setTimeout setInterval setImmediate【三个货不讨喜】 塞入一个新的macrotasks,依次:setTimeout ,setInterval --》setImmediate
1 setImmediate(function(){ 2 console.log(1); 3 },0); 4 setTimeout(function(){ 5 console.log(2); 6 },0); 7 new Promise(function(resolve){ 8 console.log(3); 9 resolve(); 10 console.log(4); 11 }).then(function(){ 12 console.log(5); 13 }); 14 console.log(6); 15 process.nextTick(function(){ 16 console.log(7); 17 }); 18 console.log(8); 19 20 // 结果是:3 4 6 8 7 5 2 1
11、Vue.js 中 nextTick 的实现原理
当观察到数据变化时,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际(已去重的)工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MutationObserver,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
Vue源码详解之nextTick:MutationObserver只是浮云,microtask才是核心! · Issue #6 · Ma63d/vue-analysis
12、Vue 实现原理
vue
将data
初始化为一个Observer
并对对象中的每个值,重写了其中的get
、set
,data
中的每个key
,都有一个独立的依赖收集器。- 在
get
中,向依赖收集器添加了监听 - 在mount时,实例了一个
Watcher
,将收集器的目标指向了当前Watcher
- 在
data
值发生变更时,触发set
,触发了依赖收集器中的所有监听的更新,来触发Watcher.update
1 const Observer = function(data) { 2 // 循环修改为每个属性添加get set 3 for (let key in data) { 4 defineReactive(data, key); 5 } 6 } 7 8 const defineReactive = function(obj, key) { 9 // 局部变量dep,用于get set内部调用 10 const dep = new Dep(); 11 // 获取当前值 12 let val = obj[key]; 13 Object.defineProperty(obj, key, { 14 // 设置当前描述属性为可被循环 15 enumerable: true, 16 // 设置当前描述属性可被修改 17 configurable: true, 18 get() { 19 console.log(\'in get\'); 20 // 调用依赖收集器中的addSub,用于收集当前属性与Watcher中的依赖关系 21 dep.depend(); 22 return val; 23 }, 24 set(newVal) { 25 if (newVal === val) { 26 return; 27 } 28 val = newVal; 29 // 当值发生变更时,通知依赖收集器,更新每个需要更新的Watcher, 30 // 这里每个需要更新通过什么断定?dep.subs 31 dep.notify(); 32 } 33 }); 34 } 35 36 const observe = function(data) { 37 return new Observer(data); 38 } 39 40 const Vue = function(options) { 41 const self = this; 42 // 将data赋值给this._data,源码这部分用的Proxy所以我们用最简单的方式临时实现 43 if (options && typeof options.data === \'function\') { 44 this._data = options.data.apply(this); 45 } 46 // 挂载函数 47 this.mount = function() { 48 new Watcher(self, self.render); 49 } 50 // 渲染函数 51 this.render = function() { 52 with(self) { 53 _data.text; 54 } 55 } 56 // 监听this._data 57 observe(this._data); 58 } 59 60 const Watcher = function(vm, fn) { 61 const self = this; 62 this.vm = vm; 63 // 将当前Dep.target指向自己 64 Dep.target = this; 65 // 向Dep方法添加当前Wathcer 66 this.addDep = function(dep) { 67 dep.addSub(self); 68 } 69 // 更新方法,用于触发vm._render 70 this.update = function() { 71 console.log(\'in watcher update\'); 72 fn(); 73 } 74 // 这里会首次调用vm._render,从而触发text的get 75 // 从而将当前的Wathcer与Dep关联起来 76 this.value = fn(); 77 // 这里清空了Dep.target,为了防止notify触发时,不停的绑定Watcher与Dep, 78 // 造成代码死循环 79 Dep.target = null; 80 } 81 82 const Dep = function() { 83 const self = this; 84 // 收集目标 85 this.target = null; 86 // 存储收集器中需要通知的Watcher 87 this.subs = []; 88 // 当有目标时,绑定Dep与Wathcer的关系 89 this.depend = function() { 90 if (Dep.target) { 91 // 这里其实可以直接写self.addSub(Dep.target), 92 // 没有这么写因为想还原源码的过程。 93 Dep.target.addDep(self); 94 } 95 } 96 // 为当前收集器添加Watcher 97 this.addSub = function(watcher) { 98 self.subs.push(watcher); 99 } 100 // 通知收集器中所的所有Wathcer,调用其update方法 101 this.notify = function() { 102 for (let i = 0; i < self.subs.length; i += 1) { 103 self.subs[i].update(); 104 } 105 } 106 } 107 108 const vue = new Vue({ 109 data() { 110 return { 111 text: \'hello world\' 112 }; 113 } 114 }) 115 116 vue.mount(); // in get 117 vue._data.text = \'123\'; // in watcher update /n in get
13、跨域解决方案
1、 通过jsonp跨域
2、 document.domain + iframe跨域
3、 location.hash + iframe
4、 window.name + iframe跨域
5、 postMessage跨域
6、 跨域资源共享(CORS)
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket协议跨域
16、前端优化的方法
参考:
http://www.ayqy.net/blog/%E5%89%8D%E7%AB%AF%E4%BC%98%E5%8C%96%EF%BC%9A%E9%9B%85%E8%99%8E35%E6%9D%A1/
以上是关于JavaScript interview memo的主要内容,如果未能解决你的问题,请参考以下文章
javascript 位数组https://github.com/owocki/Interview_prep_checklist#some-bit-hacks