[关闭]
@Bios 2019-06-27T10:59:57.000000Z 字数 9542 阅读 782

详解一套面试题

面试题


span的display值,文本example的颜色

  1. <div class="outside">
  2. <span id="passage" style="color:blue;" data-color="red">example</span>
  3. </div>
  4. <style>
  5. #passage { color: yellow;}
  6. .outside span{ color: green; display: block;}
  7. span { display: inline;}
  8. [data-color="red"] { color: red;}
  9. </style>

其实浏览器中,这张图的排列顺序,就很好的表示出了这个demo中的优先级关系:

优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器。 ⚠️!important是个例外,优先级最高。

更详细的CSS优先级请查看MDN-优先级是如何计算的?

写一个满屏的品字

这就是考验一个布局的能力,没什么好说的,办法很多。我用的flex打个样。

  1. <div class="main">
  2. <div class="top"><h1>class="top"</h1></div>
  3. <div class="bottom">
  4. <div class="left">
  5. <h1>class="left"</h1>
  6. </div>
  7. <div class="right">
  8. <h1>class="right"</h1>
  9. </div>
  10. </div>
  11. </div>
  12. <style>
  13. .main{
  14. display: flex;
  15. flex-direction: column;
  16. }
  17. .top,.bottom{
  18. height: 300px;
  19. }
  20. .top{
  21. border: 1px solid red;
  22. }
  23. .bottom{
  24. display: flex;
  25. border: 1px solid green;
  26. }
  27. .left,.right{
  28. flex: 1;
  29. height: 100%;
  30. }
  31. .left{
  32. border-right: 1px solid blue;
  33. }
  34. </style>

如下代码,写出执行结果

  1. var fun = function(arr) {
  2. for(var i = 0; i< arr.length;i++) {
  3. setTimeout(function() {
  4. console.log(i);
  5. },0)
  6. }
  7. console.log(arr[i])
  8. }
  9. fun([1,2,3,4])

直接写答案就没什么意思了,借这个题先扯一下执行上下文作用域作用域链闭包

执行上下文

以下demo、图示、结论绝大部分来自这个网站,推荐阅读!在这里引用是为了让大家更好的理解,我确实讲不了这么好!!!

一段JavaScript的代码执行的时候,都会产生一个执行上下文(也就是执行环境)。多段代码执行就会产生多个执行上下文。

  1. console.log(1);
  2. // 这段代码的执行上下文就是--全局环境
  1. function test() {
  2. console.log('test');
  3. }
  4. test();
  5. // test() 执行上下文就是test--函数环境

JavaScript中的运行环境大概包括三种情况:

  1. 全局环境:JavaScript代码运行起来会首先进入该环境
  2. 函数环境:当函数被调用执行时,会进入当前函数中执行代码
  3. eval(不建议使用,可忽略)

⚠️JavaScript引擎会以栈的形式来处理这些执行上下文,栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文

看下面这个demo,相信大家一看就懂了:

  1. var color = 'blue';
  2. function changeColor() {
  3. var anotherColor = 'red';
  4. function swapColors() {
  5. var tempColor = anotherColor;
  6. anotherColor = color;
  7. color = tempColor;
  8. }
  9. swapColors();
  10. }
  11. changeColor();

这里面有全局上下文(Global Context)changeColor()上下文swapColors()上下文,它们进栈出栈如下图:

每一个执行上下文都有自己的生命周期:

对执行上下文总结一些结论:

  1. 单线程
  2. 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
  3. 全局上下文只有唯一的一个,它在浏览器关闭时出栈
  4. 函数的执行上下文的个数没有限制
  5. 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

作用域、作用域链与闭包

作用域与执行上下文是完全不同的两个概念。

JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

⚠️JavaScript中只有全局作用域与函数作用域(因为eval我们平时开发中几乎不会用到它,这里不讨论)。

作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

看一个demo:

  1. var a = 20;
  2. function test() {
  3. var b = a + 10;
  4. function innerTest() {
  5. var c = 10;
  6. return b + c;
  7. }
  8. return innerTest();
  9. }
  10. test();

在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。

  1. innerTestEC = {
  2. VO: {...}, // 变量对象
  3. scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链
  4. }

至于这里面的VO AO 有兴趣的可以去上面那个网站里看看,这里不提,我觉得不妨碍大家理解。

简单说就是,在innerTest这个方法内,能拿到test()方法中的变量,也能拿到全局环境中的变量,这就形成了一个作用域链。

看到这里相信大家都知道了,闭包不就是这个东东嘛。

它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。
当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。

JavaScript拥有自动的垃圾回收机制,关于垃圾回收机制,有一个重要的行为,那就是,当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。

而我们知道,函数的执行上下文,在执行完毕之后,生命周期结束,那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。可是闭包的存在,会阻止这一过程。

setTimeout

绕了一圈回到这个题,这个题中的setTimeout又在何时执行呢?

在这里,将会介绍另外一个特殊的队列结构,页面中所有由setTimeout定义的操作,都将放在同一个队列中依次执行。

而这个队列执行的时间,需要等待到函数调用栈清空之后才开始执行。即所有可执行代码执行完毕之后,才会开始执行由setTimeout定义的操作。而这些操作进入队列的顺序,则由设定的延迟时间来决定。

答案undefined , 4 ,4 ,4 ,4

如下代码,写出执行结果

  1. function person(name) {
  2. if(name) {
  3. this.name = name;
  4. }
  5. console.log(this.name);
  6. }
  7. person.prototype.name = 'Tom';
  8. var human = {
  9. person: person,
  10. name: 'Cat'
  11. }
  12. person();
  13. person('Jack');
  14. new person();
  15. new person('Rose');
  16. human.person();
  17. person.call(window)

答案: undefined、Jack、Tom、Rose、Cat、Jack

如下代码,写出执行结果

  1. var a = window.a = 'finget.github.io'
  2. function hello(){
  3. console.log(a);
  4. var a = 'hello';
  5. console.log(a);
  6. console.log(b);
  7. let b = 'finget';
  8. }
  9. hello();

这个题比较简单,主要涉及的就是变量提升,和作用域链。坑点就是第一个console.log(a)到底是打印undefined还是finget.github.io

再看看作用域链那张图:

hello()方法中定义了一个var a = 'hello',虽然在刚执行的时候,根据变量提升原则,a=undefined,但是它还是很有骨气的,只要自己有绝不往上找。那如果换成let a = 'hello'呢?

来来来试一试:

  1. var a = window.a = 'finget.github.io'
  2. function hello(){
  3. console.log(a);
  4. let a = 'hello';
  5. console.log(a);
  6. console.log(b);
  7. let b = 'finget';
  8. }
  9. hello();

暂时性死区

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

  1. var tmp = 123;
  2. if (true) {
  3. tmp = 'abc'; // ReferenceError
  4. let tmp;
  5. }

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

上面的结果就很清楚了,直接报错,后面的也不执行。

提取url的参数,以key-value形式返回

完全考正则,自己恶补吧。没办法!

  1. let getSearch = function(url) {
  2. let matched = /^(?:https?:\/\/[^?]*\?)(.*)/gi.exec(url)
  3. return matched ? matched[1] : ''
  4. }
  5. // 递归函数,循环匹配search
  6. let searchFn = function (search, query) {
  7. if (search) {
  8. let matched = /(\w+)=(\w*)/g.exec(search)
  9. if (matched) {
  10. query[matched[1]] = decodeURIComponent(matched[2])
  11. searchFn(search.slice(matched.index + matched[0].length), query)
  12. }
  13. }
  14. }
  15. let parseUrl = function (url) {
  16. let query = {}
  17. searchFn(getSearch(url), query)
  18. return query
  19. }
  20. let url = 'http://localhost:3009/h5/test?recordID=161851&order=2'
  21. console.log(parseUrl(url)) // => { recordID: '161851', order: '2' }

判断一个字符串中出现最多的字符,统计次数

  1. function maxStr(str) {
  2. let map = {}
  3. for(let v of str) {
  4. map[v] = ~~map[v] + 1
  5. }
  6. // 这里就类似这种结构 map={a:1,b:1}, ~~map[v]就类似parseInt(),如果某一个字符第一出现就是0,=> 0+1,以此类推!
  7. // Object.values 能将一个对象的value返回成一个数组,再去最大值
  8. let max = Math.max(...Object.values(map))
  9. for (let key in map) {
  10. if (map[key] == max){
  11. return {[key]: max}
  12. }
  13. }
  14. }
  15. let str = 'aasdfasd,asdfjaslkdfjiqjwioaklsdf,asd,lqwejrio1ji3wioqjroiqqewslkasm'
  16. console.log(maxStr(str))
  1. 按位非运算符“~”
  2. 先看看w3c的定义:
  3. 位运算 NOT 由否定号(~)表示,它是 ECMAScript 中为数不多的与二进制算术有关的运算符之一。
  4. 位运算 NOT 是三步的处理过程:
  5. 把运算数转换成 32 位数字
  6. 把二进制数转换成它的二进制反码(0->1, 1->0
  7. 把二进制数转换成浮点数
  8. 简单的理解,对任一数值 x 进行按位非操作的结果为 -(x + 1)
  9. console.log('~null: ', ~null); // => -1
  10. console.log('~undefined: ', ~undefined); // => -1
  11. console.log('~0: ', ~0); // => -1
  12. console.log('~{}: ', ~{}); // => -1
  13. console.log('~[]: ', ~[]); // => -1
  14. console.log('~(1/0): ', ~(1/0)); // => -1
  15. console.log('~false: ', ~false); // => -1
  16. console.log('~true: ', ~true); // => -2
  17. console.log('~1.2543: ', ~1.2543); // => -2
  18. console.log('~4.9: ', ~4.9); // => -5
  19. console.log('~(-2.999): ', ~(-2.999)); // => 1
  20. 那么, ~~x就为 -(-(x+1) + 1) 相当于是 parseInt()
  21. console.log('~~null: ', ~~null); // => 0
  22. console.log('~~undefined: ', ~~undefined); // => 0
  23. console.log('~~0: ', ~~0); // => 0
  24. console.log('~~{}: ', ~~{}); // => 0
  25. console.log('~~[]: ', ~~[]); // => 0
  26. console.log('~~(1/0): ', ~~(1/0)); // => 0
  27. console.log('~~false: ', ~~false); // => 0
  28. console.log('~~true: ', ~~true); // => 1
  29. console.log('~~1.2543: ', ~~1.2543); // => 1
  30. console.log('~~4.9: ', ~~4.9); // => 4
  31. console.log('~~(-2.999): ', ~~(-2.999)); // => -2

实现一个拷贝函数

JSON.parse()

  1. const newObj = JSON.parse(JSON.stringify(oldObj));

️1.他无法实现对函数 、RegExp等特殊对象的克隆
2.会抛弃对象的constructor,所有的构造函数会指向Object
3.对象有循环引用,会报错

比较完善的深拷贝

我觉得面试手写这个也太那啥了!

  1. const isType = (obj, type) => {
  2. if (typeof obj !== 'object') return false;
  3. const typeString = Object.prototype.toString.call(obj);
  4. let flag;
  5. switch (type) {
  6. case 'Array':
  7. flag = typeString === '[object Array]';
  8. break;
  9. case 'Date':
  10. flag = typeString === '[object Date]';
  11. break;
  12. case 'RegExp':
  13. flag = typeString === '[object RegExp]';
  14. break;
  15. default:
  16. flag = false;
  17. }
  18. return flag;
  19. };
  20. const getRegExp = re => {
  21. var flags = '';
  22. if (re.global) flags += 'g';
  23. if (re.ignoreCase) flags += 'i';
  24. if (re.multiline) flags += 'm';
  25. return flags;
  26. };
  27. const clone = parent => {
  28. // 维护两个储存循环引用的数组
  29. const parents = [];
  30. const children = [];
  31. const _clone = parent => {
  32. if (parent === null) return null;
  33. if (typeof parent !== 'object') return parent;
  34. let child, proto;
  35. if (isType(parent, 'Array')) {
  36. // 对数组做特殊处理
  37. child = [];
  38. } else if (isType(parent, 'RegExp')) {
  39. // 对正则对象做特殊处理
  40. child = new RegExp(parent.source, getRegExp(parent));
  41. if (parent.lastIndex) child.lastIndex = parent.lastIndex;
  42. } else if (isType(parent, 'Date')) {
  43. // 对Date对象做特殊处理
  44. child = new Date(parent.getTime());
  45. } else {
  46. // 处理对象原型
  47. proto = Object.getPrototypeOf(parent);
  48. // 利用Object.create切断原型链
  49. child = Object.create(proto);
  50. }
  51. // 处理循环引用
  52. const index = parents.indexOf(parent);
  53. if (index != -1) {
  54. // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
  55. return children[index];
  56. }
  57. parents.push(parent);
  58. children.push(child);
  59. for (let i in parent) {
  60. // 递归
  61. child[i] = _clone(parent[i]);
  62. }
  63. return child;
  64. };
  65. return _clone(parent);
  66. };

查找素数

试除法

这种方式很传统理解上也简单,给定一个范围,那么就逐个循环去试除小于它数。

现在我们假设 N 等于 120

  1. let N = 120;
  2. let primes = [];
  3. // 用于存素数结果集
  4. loop:for(let x=2;x<=N;x++){
  5. for(let k=2;k<x;k++){
  6. if(x%k==0) continue loop;
  7. //一旦有被小于它的数整除,则退出试下一个数
  8. }
  9. //能走到这一步的就是素数了
  10. primes.push(x);
  11. }
  12. console.log(primes.join(','))

筛法

先把所有2的倍数去掉,然后剩下的那些数里面,最小的是3,3就是素数,然后把3的倍数都去掉,剩下的数里面,最小的是5,所以5也是素数…(可以看出已跳过4的试除,越多到后面跳过的数越多)

上述过程依次进行,但不像试除法逐个进行,就可以把某个范围内的非素数全都除去,剩下的就是素数了。这种方式的好处在于运算不重复,高效。

有一张很形象的动画,能直观地体现出筛法的工作过程。 (非素数就像被筛子筛掉一样)

  1. let N = 120;
  2. let primes = [];
  3. // 用于存素数结果集
  4. let nums = [];
  5. // 待筛选的数据集
  6. for(let x=2;x<=N;x++){
  7. //hooyes提示:此处初始化的时候,也可直接筛掉2的倍数数据减半。
  8. //if(x%2!==0)
  9. nums.push(x);
  10. }
  11. // 递归函数
  12. function PrimeFn(data){
  13. let p = data.shift();
  14. // 数组最前端的一个数即素数,拿出来存起,并作为下次筛除的分母。
  15. primes.push(p);
  16. let t = [];
  17. for(let v of data){
  18. v%p!==0 ? t.push(v) : ""
  19. // 能被 p 整除的都筛除掉,不能整除的放到临时数组t存起来。
  20. }
  21. // t 是下次待筛数组,元素个数会越来越少,若还有就进行一次递归。
  22. t.length>0 ? PrimeFn(t) : ""
  23. }
  24. PrimeFn(nums);
  25. console.log(primes.join(','));
  26. /*
  27. 得到小于N的素数集合
  28. 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113
  29. */

原文地址(https://hooyes.net/p/javascript-prime-number)[https://hooyes.net/p/javascript-prime-number]

处理金额

  1. /**
  2. * 金额三位一划分
  3. * @param {[string/number]} money [金额]
  4. * @param {[string/number]} round [小数位]
  5. * @param {[any]} flag [是否四舍五入]
  6. * @return {[type]} [description]
  7. */
  8. function formatMoney(money,round,flag) {
  9. money = Number(money);
  10. round = Number(round);
  11. let formatReg = /(\d)(?=(\d{3})+\.)/g;
  12. let sliceReg = new RegExp (`([0-9]+\.[0-9]{${round}})[0-9]*`);
  13. if(!isNaN(money)&&Object.prototype.toString.call(money).slice(8,-1) === 'Number') {
  14. if (!isNaN(round)&&flag) {
  15. return String(money.toFixed(round)).replace(formatReg,'$1,')
  16. } else if(!isNaN(round)){
  17. return String(money).replace(sliceReg,'$1').replace(formatReg,'$1,')
  18. } else if(round === 'undefined'){
  19. return String(money).replace(formatReg,'$1,')
  20. } else {
  21. throw new Error('round is not Number!')
  22. }
  23. } else {
  24. throw new Error('money is not Number!')
  25. }
  26. }
  27. let res = formatMoney('1987562.12812',3,true)
  28. console.log(res)
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注