V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
southSu
V2EX  ›  分享创造

曾经面试踩过的坑,都在这里了~ Javascript

  •  
  •   southSu ·
    meibin08 · 2018-11-04 15:27:58 +08:00 · 1432 次点击
    这是一个创建于 2215 天前的主题,其中的信息可能已经有所发展或是发生改变。

    接上一篇的 曾经面试踩过的坑,都在这里了~ HTML/CSS

    Javascript

    1、请将下列 b 函数进行修改,保证每次调用 a 都能+1 (考闭包):
    //本文由 IT@IT·平头哥联盟-首席填坑官∙苏南分享,如有错误,欢迎留言
    function b(){
    	var a=1;
    };
    
    function b(){
    	var a=1;
    	return ()=>{
    		a++;
    		return a;
    	}
    };
    let c = b();
    c(); //2
    c(); //3
    c(); //4
    

    ####### 2、js 有哪些基本数据类型:    ECMAScript 标准定义有 7 种数据类型:

    • Boolean
    • Null
    • Undefined
    • Number
    • String
    • Symbol :(ECMAScript 6 新定义 ,Symbol 生成一个全局唯一、表示独一无二的值)
    • Object :( Array、Function、Object )
    3、用 js 将 386485473.88 转换为 386,485,473.88 (千位分割符):
    //方法 1:
    var separator=(num)=>{
    	if(!num){
    		return '0.00';
    	};
    	let str = parseFloat(num).toFixed(2);
    	return str && str
    		.toString()
    		.replace(/(\d)(?=(\d{3})+\.)/g, function($0, $1) {
    			return $1 + ",";
    		});
    }
    
    separator(386485473.88) //"386,485,473.88"
    
    //方法 2:
    (386485473.88).toLocaleString('en-US')  // "386,485,473.88" 由 (sRect)补充
    
    
    4、js 的 for 跟 for in 循环它们之间的区别?
    • 遍历数组时的异同: for循环 数组下标的 typeof 类型:number, for in 循环数组下标的 typeof 类型:string;
    var southSu = ['苏南','深圳','18','男'];
    for(var i=0;i<southSu.length;i++){
    	console.log(typeof i); //number
    	console.log(southSu[i]);// 苏南 , 深圳 , 18 , 男
    }
    var arr = ['苏南','深圳','18','男','帅气',"@IT·平头哥联盟-首席填坑官"];
    for(var k in arr){
    	console.log(typeof k);//string
    	console.log(arr[k]);// 苏南 , 深圳 , 18 , 男 , 帅气,@IT·平头哥联盟-首席填坑官
    }
    
    
    • 遍历对象时的异同:for循环 无法用于循环对象,获取不到 obj.length; for in 循环遍历对象的属性时,原型链上的所有属性都将被访问,解决方案:使用hasOwnProperty方法过滤或 Object.keys 会返回自身可枚举属性组成的数组
    Object.prototype.test = '原型链上的属性,本文由 @IT·平头哥联盟-首席填坑官∙苏南分享';
    var southSu = {name:'苏南',address:'深圳',age:18,sex:'男',height:176};
    for(var i=0;i<southSu.length;i++){
    	console.log(typeof i); //空
    	console.log(southSu[i]);//空
    }
    
    
    for(var k in southSu){
    	console.log(typeof k);//string
    	console.log(southSu[k]);// 苏南 , 深圳 , 18 , 男 , 176 ,本文由 @IT·平头哥联盟-首席填坑官∙苏南分享
    }
    
    
    5、给 table 表格中的每个 td 绑定事件,td 数量为 1000+,写一下你的思路(事件委托题):
    <body class="container">
    	<table id="table">
    		<tr><td>我们是 @IT·平头哥联盟</td><td>,我是首席填坑官</td><td>苏南</td><td>前端开发</td><td>优秀</td></tr>
    		<tr><td>我们是 @IT·平头哥联盟</td><td>,我是首席填坑官</td><td>苏南</td><td>前端开发</td><td>优秀</td></tr>
    		<tr><td>我们是 @IT·平头哥联盟</td><td>,我是首席填坑官</td><td>苏南</td><td>前端开发</td><td>优秀</td></tr>
    		…………
    	</table>
    <script>
    	let table =document.querySelector("#table");
    	table.addEventListener("click",(e)=>{
    		let {nodeName} = e.target;
    		if(nodeName.toUpperCase() === "TD"){
    			console.log(e.target);//<td>N</td>
    		}
    	},false);
    
    </script>
    </body>
    
    
    6、js 把一串字符串去重(能统计出字符重复次数更佳),列出你的思路(两种以上):
    <script>
    	let str = "12qwe345671dsfa233dsf9876ds243dsaljhkjfzxcxzvdsf 本文由 @IT·平头哥联盟-首席填坑官∙苏南分享";
    	let array = str.split("");
    
    	//方案一:
    	array = [...new Set(array)].join("");
    	array = ((a)=>[...new Set(a)])(array).join("");
    	console.log(array);//12qwe34567dsfa98ljhkzxcv 本文由 @IT·平头哥联盟-首席填坑官∙苏南分享  只能过滤,不会统计
    
    	//方案二:
    	function unique (arr) {
    		const seen = new Map()
    		return (arr.filter((a) => !seen.has(a) && seen.set(a, 1))).join("");
    	}
    	console.log(unique(array)) // 12qwe34567dsfa98ljhkzxcv 本文由 @IT·平头哥联盟-首席填坑官∙苏南分享
    
    	//方案三:
    	function unique (arr) {
    		let arrs=[];
    		var news_arr = arr.sort();//排序能减少一次循环
    		for(var i=0;i<news_arr.length;i++){
    				if(news_arr[i] == news_arr[i+1] && news_arr[i]!= news_arr[i-1] ){
    						arrs.push(arr[i]);
    				};
     
    		};
    		return arrs.join("");
    	}
    	console.log(unique(array)) // 12qwe34567dsfa98ljhkzxcv 本文由 @IT·平头哥联盟-首席填坑官∙苏南分享
    
    	//方案四:
    	function unique (arr) {
    		let obj={};
    		for(var i=0;i<arr.length;i++){
    			let key = arr[i];
    			if(!obj[key] ){
    					obj[key]=1;
    			}else{
    				obj[key]+=1;
    			}
     
    		};
    		return obj;
    	}
    	console.log(unique(array)) // object 对应每个 key 以及它重复的次数 
    
    </script>
    
    
    7、项目上线前,你们做过哪些性能优化:
    • 图片预加载,css 样式表放在顶部且 link 链式引入,javascript 放在底部 body 结束标签前;
    • 使用 dns-prefetch 对项目中用到的域名进行 DNS 预解析,减少 DNS 查询,如: <link href="//github.com" rel="dns-prefetch">;
    • 减少 http 请求次数:图片静态资源使用 CDN 托管;
    • API 接口数据设置缓存,CSS Sprites/SVG Sprites(如有疑惑:该如何以正确的姿势插入 SVG Sprites? 这篇说的很详细), JS、CSS 源码压缩、图片大小控制合适,使用 iconfont(+ 字体图标)或 SVG,它们比图片更小更清晰,网页 Gzip 压缩;
    • 减少 DOM 操作次数,优化 javascript 性能;
    • 减少 DOM 元素数量,合理利用:after、:before 等伪类;
    • 避免重定向、图片懒加载;前后端分离开发,资源按需加载,最好能做到首屏直出(即服务端渲染);
    • 避免使用 CSS Expression ( css 表达式)又称 Dynamic properties(动态属性) ;
    • 多域名分发划分内容到不同域名,解决浏览器域名请求并发数问题,同时也解决了请求默认携带的 cookie 问题;
    • 尽量减少 iframe 使用,它会阻塞主页面的渲染; 对所有资源压缩 JavaScript、CSS、字体、图片等,甚至 html;
    • 只想到这些,欢迎补充……
    8、你对重绘、重排的理解?
    • 首先网页数次渲染生成时,这个可称为重排;
    • 修改 DOM、样式表、用户事件或行为(鼠标悬停、页面滚动、输入框键入文字、改变窗口大小等等)这些都会导致页面重新渲染,那么重新渲染,就需要重新生成布局和重新绘制节点,前者叫做"重排",后者"重绘";
    • 减少或集中对页面的操作,即多次操作集中在一起执行;
    • 总之可以简单总结为:重绘不一定会重排,但重排必然为会重绘。
    • 更详细的可以看阮老师分析
    8、有用过promise吗?请写出下列代码的执行结果,并写出你的理解思路:
    setTimeout(()=>{
    		console.log(1);
    }, 0);
    
    new Promise((resolve)=>{
    		console.log(2);
    		for(var i = 1; i < 200; i++){
    				i = 198 && resolve();
    		}
    		console.log(3);
    }).then(()=>{
    		console.log(4);
    });
    console.log(5);
    
    // 结果:2、3、5、4、1;
    
    • 首先要讲一下,js 是单线程执行,那么代码的执行就有先后;
    • 有先后,那就要有规则(排队),不然就乱套了,那么如何分先后呢?大体分两种:同步、异步;
    • 同步很好理解,就不用多说了(我就是老大,你们都要给我让路);
    • 异步(定时器[setTimeout,setInterval]、事件、ajax、promise 等),说到异步又要细分宏任务、微任务两种机制,
    • 宏任务:js 异步执行过程中遇到宏任务,就先执行宏任务,将宏任务加入执行的队列(event queue),然后再去执行微任务;
    • 微任务:js 异步执行过程中遇到微任务,也会将任务加入执行的队列(event queue),但是注意这两个 queue 身份是不一样的,不是你先进来,就你先出去的(就像宫里的皇上选妃侍寝一样,不是你先进宫(或先来排队)就先宠幸的 ),真执行的时候是先微任务里拿对应回调函数,然后才轮到宏任务的队列回调执行的;
    • 理解了这个顺序,那上面的结果也就不难懂了。

    说细步骤如下: setTimeout 是异步,不会立即执行,加入执行队列; new Promise 会立即执行 输出 2、3,而在 2、3 之间执行了 resolve 也就是微任务; 再到 console.log(5)了,输出 5; 然后异步里的微任务先出,那就得到 4; 最后执行宏任务 setTimeout 输出 1; 如有错误欢迎纠正!

    9、new SouthSu() 在这个过程中都做了些什么?
    function SouthSu(){
     		this.name = "苏南";
     		this.age = 18;
     		this.address = "深圳";
     		this.address = "首席填坑官";
    };
    
     let South = new SouthSu();
     console.log(South,South.__proto__ === SouthSu.prototype) //true 
    
    执行过程:
    创建一个空的对象
     let p1 = new Object();
    
    
    设置原型链
    	p1.__proto__ = SouthSu.prototype;
    
    让 构造函数 的 this 指向 p1 这个空对象
    
    	let funCall = SouthSu.call(p1);
    
    处理 构造函数 的返回值:判断 SouthSu 的返回值类型,如果是值类型则返回 obj,如果是引用类型,就返回这个引用类型的对象;
    
    
    10、工作中如果让你使用 js 实现一个持续的动画,你会怎么做(比如转盘抽奖)??
    • js 来实现动画,第一时间想到的就是定时器(setTimeout、setInterval);
    • 后面想起来 js 有个 window.requestAnimationFrame,当时只是说了记得有这么一个 API,具体的细节没能答上,面试官直言想听的就是这个 API 的使用,好吧是我准备的不够充分,希望其他同学不再犯同样错误;

    window.requestAnimationFrame() 方法告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用,回调的次数通常是每秒 60 次,是大多数浏览器通常匹配 W3C 所建议的刷新频率。在大多数浏览器里,当运行在后台标签页或者隐藏的<iframe> 里时,requestAnimationFrame() 会暂停调用以提升性能和电池寿命。

    小结:以往项目开发中大数人可能都是第一时间选择 JS 定时器setInterval 或者setTimeout 来控制的动画每隔一段时间刷新元素的状态,来达到自己所想要的动画效果,但是这种方式并不能准确地控制动画帧率,因为这是开发者主动要求浏览器去绘制,它这可能会因为动画控制的时间、绘制的频率、浏览器的特性等而导致丢帧的问题; requestAnimationFrame 是浏览器什么时候要开始绘制了浏览器它自己知道,通过requestAnimationFrame告诉我们,这样就不会出现重复绘制丢失的问题。

    //一个持续旋转的正方形,
    <div class="angle-div"></div>
    <script>
    	let timer = null;
    	let Deg = 0;
    	let distance = 360;
    	var _requestAnimationFrame_ = window.requestAnimationFrame || window.webkitRequestAnimationFrame;//本文由 @IT·平头哥联盟-首席填坑官∙苏南分享
    	let angleDiv = document.querySelector(".angle-div");
    	cancelAnimationFrame(timer);
    	let fn = ()=>{
    		if(Deg < distance){ 
    			Deg++;
    		}else{
    			Deg=0;
    		};
    		angleDiv.style.transform = `rotateZ(${Deg}deg) translateZ(0)`; 
    		angleDiv.style.WebkitTransform = `rotateZ(${Deg}deg) translateZ(0)`;
    		timer = _requestAnimationFrame_(fn);
    	}
    	timer = _requestAnimationFrame_(fn);
    </script>
    
    

    蜜獾(huan),绰号 平头哥 被称为最无所畏惧的动物

    11、如何设置 http 缓存?

    1)、Expires

    • Expires的值为服务端返回的到期时间,响应时告诉浏览器可以直接从浏览器缓存中读取无需再次请求。 缺点:返回的是服务端的时间,比较的时间是客户端的时间,如果时间不一致有可能出现错误。

    2)、Cache-Control

    • Cache-Control可设置的字段有:
    • private:客户端可以缓存
    • public:客户端和代理服务器都可以缓存
    • max-age=xxx:缓存内容在 xxx 秒后失效
    • no-cache:需要用另一种缓存策略来验证缓存(ETag,Last-Modified)
    • no-store:不进行缓存
    • Last-Modified:浏览器请求获得文件后,服务器返回该文件的最后修改时间Last-Modified,下一次请求会带上If-Modified-Since标识,如果If-Modified-Since等于服务器的文件修改时间,则表示文件没有修改,返回 304 状态码,浏览器从浏览器缓存中读取文件。如果If-Modified-Since小于服务端的文件修改时间,则浏览器会重新发送请求获取文件,返回状态码 200。
    • ETag:服务器文件的一个唯一标识,例如对文件内容取 md5 值作为ETag的字段返回给浏览器。当文件变化时ETag的值也会发生变化。下次请求会带上If-None-Match即浏览器保留的ETag值,如果发送了变化,则文件被修改,需要重新请求,返回 200 状态码。反之浏览器就从缓存中读取文件,返回 304 状态码。

    总结:——几者之间的关系

    • Cache-Control设置为max-age=xx并且同事设置Expires时,Cache-Control的优先级更高
    • ETagLast-Modified同时存在时,服务器先会检查ETag,然后再检查Last-Modified,最终决定返回 304 还是 200
    • 该题由 本文由 @IT·平头哥联盟-成员(ZodiacSyndicate )补充
    12、随机打乱一个数组
    • 思路:从数组的最后一项开始,随机选择前面的一个元素进行交换,然后一步步往前交换
    //该题由 本文由 @IT·平头哥联盟-成员(ZodiacSyndicate )补充
    const shuffle = arr => {
      let end = arr.length - 1
      while(end) { // 当 end 为 0 时不需要交换
        const index = Math.floor(Math.random() * (end + 1))
        [arr[index], arr[end]] = [arr[end], arr[index]]
        end -= 1
      }
      return arr
    }
    
    13、用 React 实现一个显示鼠标位置的高阶组件
    //该题由 本文由 @IT·平头哥联盟-成员(ZodiacSyndicate )补充
    const mousePosition = Component => class extends React.Component {
      state = {
        x: 0,
        y: 0,
      }
    
      handleMouseMove = e => {
        this.setState({
          x: e.clientX,
          y: e.clientY
        })
      }
    
      render() {
        const { x, y } = this.state
        return (
          <>
            <div onMouseMove={this.handleMouseMove}>
              <Component {...this.props} />
            </div>
            <span>x: {x}</span>
            <span>y: {y}</span>
          </>
        )
      }
    }
    

    文本将持续更新,整理收集自己 /群友的面经分享给大家,如果觉得不错那就请关注下方的 公众号,有惊喜哦。

    宝剑锋从磨砺出,梅花香自苦寒来,做有温度的攻城狮,公众号:honeyBadger8

    作者:苏南 - 首席填坑官
    交流群:912594095,公众号:honeyBadger8
    本文原创,著作权归作者所有。商业转载请联系@IT·平头哥联盟获得授权,非商业转载请注明原链接及出处。

    3 条回复    2018-11-05 23:16:16 +08:00
    southSu
        1
    southSu  
    OP
       2018-11-04 15:29:17 +08:00
    去做想做的事,去爱值得的人;
    去成为自己喜欢的模样,
    去让自己发光!浑身充满力量,
    充实的日子最美好!——我是苏南,感谢您耐心读完此文,愿你一切安好!
    fobven
        2
    fobven  
       2018-11-05 09:28:51 +08:00
    您好,异步队列真执行的时候是先微任务里拿对应回调函数,然后才轮到宏任务的队列回调执行的,这句话不是很清楚,能请教一下详细的解释吗?
    southSu
        3
    southSu  
    OP
       2018-11-05 23:16:16 +08:00
    @fobven

    考虑一千次,不如去做一次
    犹豫一万次,不如实践一次
    将来的你,一定会感谢现在奋斗的你——晚上好,我是苏南,感谢您耐心读完此文,建议可以执行一下问题的代码,可能有助于进一步理解,也就是说队列里有两种 queue,并不是先进就先出的,setTimeout 先加入队列,resolve 后加入的,就是一个证明,晚安!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1135 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 23:00 · PVG 07:00 · LAX 15:00 · JFK 18:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.