首页
登录
原
这几道 JavaScript 的简答题你都会了吗?
820
人阅读
2020/8/4 20:54
总访问:
7387
评论:
0
手机
收藏
分类:
前端
## 一、请说出下列最终执行结果,并解释为什么 ```javascript var a = []; for (var i = 0; i < 10; i++) a[i] = function () { console.log(i); }; a[6](); // log -> 10 ``` 咦。 为什么是 **10** 。不应该是 **6** 呢。其实很简单。我们把代码重新改造一下就看能出来了 ```javascript var a = []; // ..., ..., ...,..., ..., function() {console.log(i)}, ..., ..., ..., var i; for (i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } const fn = a[6]; // function() {console.log(i)} fn(); // (1) ``` 我们都知道 `var` 定义变量是会绑定到全局的,就如改造后的代码一样。 当循环执行完之后,在 `a` 数组中存放了 10 个打印 `i` 的 **方法(函数)** 当代码执行到 **标注(1)** 位置 `fn` 时。这时打印 `i` 已经是 代码最外层的 提升过的 `var i` 了。通过 循环体的 ~~践踏~~后 `var i` 已经变成 **10** 。所以打印了 **10** > **var 关键值 是有是会绑定到全局的,在日常开发中应减少甚至不用 var 关键字** > 用 var 声明的变量的作用域是它当前的执行上下文,它可以是嵌套的函数,或者对于声明在任何函数外的变量来说是全局 > [MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/var) --- ## 二、请说出下列最终执行结果,并解释为什么 ```javascript var tmp = 123; if (true) { console.log(tmp); // ReferenceError: Cannot access 'tmp' before initialization let tmp; } ``` 当变量存在于 **块级作用域** 时 优先使用 **块级作用域** 中的变量 此处的 `console.log(tmp)` 找到块级中有一个 `tmp` ,但是此 `tmp` 定义在使用后,所以 报错 **初始化前无法访问** 错误。 > 其实这个地方还看出一个东西,那就是这里的 `let`也是有 "变量提升" ! 不信你品。 `let tmp` 处于打印之后,按代码执行流程的话 那 `console.log(tmp);` 的 `tmp` 应该 去取 `var tmp = 123;` 才对, > 但是却报出错误 `ReferenceError: Cannot access 'tmp' before initialization`, 这就说明 `let tmp;` 在这里是可以"访问"到。只不过是无法访问到 /笑哭。这 T\* 是不是有点矛盾啊 ?? > 我的想法可能不是完全正确,但我觉得 **只有 var 才有 "变量提升"** 的说法是可以再 **推敲推敲** 的。感谢兴趣的朋友去百度百度 --- ## 三、结合 ES6 新语法,用最简单的方式找出数组中的最小值 ```javascript var arr = [12, 34, 32, 89, 4]; var arr2 = [12, 34, 32, 89, 4]; const result1 = arr.sort((a, b) => a > b).pop(); const result2 = Math.min(...arr2); console.log(result1, result2); // 4 4 ``` --- ## 四、请详细说明 `var let const` 三种声明方式之间的具体差别 ### var:JavaScript 最古老的定义变量方式。可在定义前使用。在任何地方定义都将绑定到全局 ```javascript // 可在定义前使用 console.log(val); // undefined var val; // 示例2 console.log(val); // 100 var val = 100; // 在任何地方定义都将绑定到全局 console.log(val2); undefined if (true) { var val2 = 99; } console.log(val2); 99 } ``` ### let:ES6 (ECMAScript 2015) 新定义变量方式,块级作用域。不会绑定到全局 ```javascript // 不可在定义前使用 console.log(val); // Cannot access 'val' before initialization let val; // 示例2 console.log(val); // Cannot access 'val' before initialization let val1 = 100; // 不会绑定到全局 块级作用域 console.log(val2); //val2 is not defined if (true) { let val2 = 99; } console.log(val2); // val2 is not defined ``` ### const:ES6 (ECMAScript 2015) 定义变量时必须初始化值。此变量之后不可再次被赋值,其他特性与 `let` 相同 ```javascript // 定义变量时必须初始化值 const name = 1; console.log(name); // Missing initializer in const declaration const name2 = "tom"; console.log(name2); // tom // 不可再次被赋值 const name3 = "tom"; console.log(name3); // tom name3 = "Baboon"; // Assignment to constant variable. ``` > 注意 `const` 如果初始化的是一个对象(引用类型),该对象的 **成员(属性)** 可以更改 > > ```javascript > const obj = { a: 1, b: 2 }; > obj.a = 99; // 可以 > obj = { c: 3, d: 4 }; // 报错 Assignment to constant variable. > ``` ### 整个表格看下 | 特性 | var | let | const | | :----------------- | --- | --- | ----- | | 是否绑定全局作用域 | 是 | 否 | 否 | | 是否块级作用域 | 否 | 是 | 是 | | 是否可以重复声明 | 是 | 否 | 否 | --- ## 五、请说除下列代码的最终输出结果,并解释为什么 ```javascript var a = 10; var obj = { a: 20, fn() { setTimeout(() => { console.log(this.a); }); }, }; obj.fn(); // 20 ``` 头发不多同学应该都知道 **谁是调用者 this 就指向谁** 但是这里有个 **箭头函数** 混淆视听。不过只要记住 **箭头函数** 跟 `this` 一点关系也没有。**箭头函数** 也没有 `this`。问题也就迎刃而解了。 > 愿天下再无 **this** 问题 --- ## 六、简述 `Symbol` 类型的用途 Symbol 是 ES6 中引入的新数据类型,它表示一个唯一的常量,通过 Symbol 函数来创建对应的数据类型,创建时可以添加变量描述,该变量描述在传入时会被强行转换成字符串进行存储。 根据 `Symbol` 的特性 最好的用途就是 **私有对象属性** 和 **常量值** - 私有对象属性 ```javascript const privateMethod = Symbol(); this[privateMethod] = (v) => v; ``` 当需要屏蔽外部访问某个属性时可以使用这种方式 - 常量值 ```javascript const KEY = { mayun: Symbol(), mahuateng: Symbol(), leijun: Symbol(), }; function getValue(key) { switch (key) { case KEY.mayun: return "111"; case KEY.mahuateng: return "222"; case KEY.leijun: return "333"; } } getValue(KEY.baidu); ``` 在有些 **不关心值本身只关心值的唯一性时场景** 可以使用这种方式 --- ## 七、说说什么是浅拷贝,什么是深拷贝 首先这两个 “说法” ,说的是 **引用类型** 的拷贝 ### 浅拷贝:引用拷贝 ```javascript const obj = { a: 1, b: 2 }; const obj2 = obj; console.log(obj === obj2); // true obj2.a = 999; console.log(obj.a); // 999 ``` 从上面代码可以看出。这是很显然的浅拷贝。 当执行 `const obj2 = obj;` 后 `obj2`,`obj` 其实都指向了 { a: 1, b: 2 },并且 **引用类型** 相比较时,比较的是 **内存地址**是否相同,所以此处打印 `true`。 当执行 `obj2.a = 999` 后 因为 `obj2`,`obj` 都指向了同一个对象。所以 `obj` 的 `a` 属性也是 999 ### 深拷贝:对象拷贝 **JSON.parse(), JSON.stringify() 方式** 缺点:无法拷贝 undefined , function, RegExp 等类型 ```javascript const obj = { a: 1, b: 2 }; const obj2 = JSON.parse(JSON.stringify(obj)); console.log(obj === obj2); // false obj2.a = 999; console.log(obj.a); // 1 console.log(obj2.a); // 999 ``` **Object.assign({}, obj)方式** 缺点:无法拷贝多层对象 ```javascript const obj = { a: 1, b: 2, c: { d: 3 } }; const obj2 = Object.assign({}, obj); console.log(obj === obj2); // false obj2.a = 999; console.log(obj.a); // 1 console.log(obj2.a); // 999 obj2.c.d = 88; // obj 和 obj2 的 c 属性 指向同一个对象 console.log(obj.c.d); // 88 console.log(obj2.c.d); // 88 ``` --- ## 八、请简述 TypeScript 与 JavaScript 之间的关系 TypeScript 的 JavaScript 超集,TypeScript 扩展了 JavaScript 简单说 TypeScript 包含了 JavaScript --- ## 九、请谈谈你所认为的 TypeScript 优缺点 ### 优点 - 微软大法真香,与自家的 vs code 强强联合 - 提高了代码可读性和可维护性 - 提供强大并丰富类型系统。在编译时即可检查出常见错误 - 容易上手 - 语法糖,一些还未被标准化的特性也可使用 ### 缺点 - 缺点,有啥缺点!快给我学。都 2020 年了 再不学 TypeScript Plus 都出来了 --- ## 十、描述引用计数的工作原理和优缺点 ### 工作原理 通过引用计数器 去保存 代码在运行时 对象的 **引用数** 只要一个对象被引用 如 `const obj = { a: 1, b: 2 };` **引用计数器** 就会增加 当一个对象移除引用时 **引用计数器** 就会减少 当某个对象的引用一个都没有时,也就是 **引用计数器** 为 0 时,GC 将会工作回收这个对象 ### 优缺点 - 因为采用引用计数方式,所以当发现 引用数为 0 时,可以立即回收空间 - 无法回收循环引用的对象 --- ## 十一、描述标记整理算法的工作流程 说到 **标记整理算法** 不得不 说下 **标记清除算法** ### 标记清除算法 1. 标记阶段: 首先递归去寻找 **可达对象**,再对可达对象进行标记, 2. 清除阶段: 然后再去把没有标记的对象进行回收。并且会清除上一轮的标记。 但是这些垃圾对象再内存中的位置不是连续的。回收后不便于程序再次申请使用。所以有 **标记清除算法** 的 Pro 版本 ### 标记整理算法 1. 标记阶段:与 **标记清除算法**一致 首先递归去寻找 **可达对象**,再对可达对象进行标记, 2. 清除阶段:先执行整理,移动对象的位置。让对象在地址上尽可能是连续的,然后再去把没有标记的对象进行回收 --- ## 十二、描述 V8 新生代存储区垃圾回收的流程 在 V8 中 GC 回收策略采用 **分代回收**的策略,称为 **新生代**, **老生代**,并且对此采用了不同回收算法。 在新生代中又被分成了 2 块等大小的空间 我们分别对其称为 `from` 和 `to`。 当程序运行时,空间使用的是 `from` 空间,而`to` 空间是空闲的,当 `from` 空间使用到一定程度时将会触发 GC 机制,即对 `from` 空间使用标记整理算法对 **活动对象** 进行标记和整理,再将`from` 空间整理出来的 **活动对象** 完整的拷贝到 `to` 空间。因为 `to` 空间 已经有了 `from` 空间中的活动对象,所以只需要把 `from` 空直接进行释放即可。再然后`from` 和 `to` 进行交换。就完成了 **新生代** 空间 的回收操作。 1. 对 `from` 空间的 **活动对象** 进行标记整理 2. 再将整理过的 **活动对象** 完整拷贝到 `to` 空间 3. 直接释放 `from` 空间 4. 交换 `from` 空间 和 `to` 空间 5. 完成新生代的垃圾回收 > 是不是有种 空间换时间 的骚操作 --- ## 十三、描述增量标记算法在何时使用及工作原理 首先我们要知道在执行 GC 操作时,我们的 JavaScript 是会暂停执行的。如果 GC 的时间太长。那我们的 JavaScript 阻塞的时间就会更长。太长时间的 阻塞 肯定是不行的。这时就需要采用 **增量标记算法** 来对 GC 操作进行优化了 ### 那 **增量标记算法** 到底是个啥呢? 它就是将一整段的 GC 操作,拆分成多个小步骤,组合着去完成当前的整个回收操作,用这种拆分的方式去替代直接进行一整段的垃圾回收操作。从而达到 JavaScript 运行 和 GC 操作的交替执行。 好处:使用这种方式。可以让之前一整段的垃圾回收操作分成了更小段,程序暂停的时间段也变小了,这样对与用户来说体验也会更好! --- > 纸上得来终觉浅 绝知此事要躬行
评价
{{titleitem}}
{{titleitem}}
{{item.content}}
{{titleitem}}
{{titleitem}}
{{item.content}}
BaboonKing
吉吉国王申请出战!
博主信息
排名
6
文章
6
粉丝
16
评论
8
文章类别
前端
11篇
最新文章
Vue Router 使用、基础回顾
Vue 使用和概念的快速回顾
如何写一个 简单的 发布订阅模式 demo
前端脚手架工具
前端工程化不能停
这几道 JavaScript 的简答题你都会了吗?
JavaScript进阶-函数式编程-纯函数
JavaScript进阶-函数式编程-闭包(Closure)
JavaScript进阶-函数式编程-高阶函数
JavaScript进阶-函数式编程-函数是一等公民
JavaScript进阶-函数式编程-概念
最新评价
{{item.articleTitle}}
{{item.blogName}}
:
{{item.content}}
关于我们
ICP备案 :
渝ICP备18016597号-1
网站信息:
2018-2020TNBLOG.NET
技术交流:
群号677373950
欢迎加群
欢迎加群交流技术