前端面试题(偏应用)整理

前情提要: 这篇是偏向应用类的题目,关于涉及到的知识点,配合个人上一篇JavaScript知识点整理实用更佳喔~javascript

HTML+CSS

布局

假定高度已知,请写出三栏布局,其中左栏、右栏宽度各为300px,中间自适应

  • 浮动
  • 绝对定位

中间元素 position:absolute;left:300px;right:300phtml

  • 弹性盒子flex

容器 display:flex; 中间元素 flex:1;java

  • 表格布局table

容器 display:table; 中间元素 display: table-cell;node

  • 网格布局grid

容器 display:grid;grid-template-rows:300px;// 行高 中间元素 grid-template-columns:300px auto 300px;android

五种方案的优缺点git

  • 浮动后脱离文档流,需清除浮动,兼容性较好
  • 绝对定位会脱离文档流且子元素也会脱离文档流,可以使用性较差
  • flex布局较完美
  • 表格布局兼容性好,但使用较麻烦且一行中全部表格等高不够灵活
  • 网格布局新技术,有兼容性问题 高度未知后,这几种方案还有哪些可以使用 flex布局和表格布局

水平垂直居中的几种实现方式

  • flex,div内子元素居中
div{
    display: flex;
    justify-content: center; // 水平居中
    align-items: center; // 垂直居中
}
复制代码
  • CSS3中的transform属性
div{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
}
复制代码
  • 绝对定位和margin-left/margin-top(仅适用宽高固定)
div{
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -0.5*宽度;
    margin-top: -0.5*高度;
}
复制代码
  • 绝对定位和margin:auto(仅适用宽高固定)
div{
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
复制代码

居中为何要用transform(为何不用margin-left/margin-top)

若是使用margin-left/margin-top须要固定元素宽/高,而transform不须要,100%就是元素自身的宽高。github

flex布局

display: flex;
// 主轴方向
flex-direction: row|row-reverse|column|column-reverse;
// 换行:wrap 不换行:nowrap
flex-wrap:nowrap|wrap|wrap-reverse;
// 主轴对齐方式
justify-content:flex-start|flex-end|center|space-between|space-around;
// 副轴对齐方式
align-items:flex-start|flex-end|center|baseline|stretch;
// 多根轴线对齐方式
align-content:flex-start|flex-end|center|space-between|space-around|stretch;
复制代码

网格布局grid

// display: grid;
// grid-template-rows // 定义行轨道
// grid-template-columns // 定义列轨道
// grid-column-gap和grid-row-gap定义网格间隙
// 实现上下网格高度为50px,100px,中间自适应
grid{
    display:grid;
    grid-template-columns:50px auto 100px;
}
//实现第一列,第二列,第三列宽度为90px,50px,150px
grid{
    display:grid;
    grid-template-rows:90px 50px 150px;
}
// 实现项目1占1/4,项目2占1/4宽度,项目3占1/2宽度
grid{
    display: grid;
    grid-template-rows: 1fr 1fr 2fr;
}
复制代码

position

position有哪些值?有什么做用

  • static:默认值,不脱离文档流
  • relative:相对定位,相对自身原来的位置定位,不会脱离文档流
  • absolute:绝对定位,会脱离文档流,若是有设置为relative的父容器则相对父容器定位,若是没有就相对body定位
  • fixed:固定定位,相对浏览器浏览器定位,会脱离文档流
  • sticky:粘性定位,基于用户滚动的位置,正常的时候像relative,当页面滚动超出目标区域时,就像fixed(IE15以前不支持)能够用来作吸顶效果
  • inherit:继承父级的position属性
  • initial:设置该属性为默认值

盒模型

每一个HTML元素均可以叫盒模型,盒模型包括margin,border,padding,content。web

标准盒模型和IE盒模型的区别

  • 标准盒模型内容大小为content的大小 box-sizing:content-box;
  • IE盒模型内容大小为content+padding+border的大小box-sizing:border-box 获取盒模型宽高
  • 获取内联元素的宽高,但样式通常不之内联方式写,因此不适用大多数开发 dom.style.width/height
  • 只适用于在IE浏览器获取元素宽高 dom.currentStyle.width/height
  • 获取元素的宽高,这个方法兼容性较好,支持Firefox,chrome window.getComputedStyle(dom).width/height
  • 根据元素在视窗中的绝对位置来获取宽高 dom.getBoundingClientRect().width/height
  • 最经常使用,兼容性最好 dom.offsetWidth/offsetHeight

行内元素和块元素

BFC

BFC块级格式化上下文,是一种边距解决方案算法

实例题(根据盒模型解释边距重叠)

两个box都设置了边距,垂直方向上两个box的边距会重叠,以绝对值大的为最终结果显示chrome

BFC的原理

BFC元素不会与float的元素重叠,BFC上下的边距不会重叠,BFC是一个独立的容器和外面的容器互不干扰,计算BFC高度的时候浮动子元素也会参与运算

如何建立BFC

  • 根元素
  • float属性不为none
  • position:absolute/fixed;
  • display:inline-block,table-cell,table-caption,flex,inline-flex;
  • overflow不为visiable,为hidden/auto

BFC的使用场景

  • 自适应两栏布局
  • 清除内部浮动
  • 防止垂直margin重叠

animation

使用CSS写一个幻灯片效果

考察点:animation

<style>
.ani{
    width:100px;
    heigth:100px;
    background-size:cover;
    background-position:center;
    animation: loop 20s infinite;
    -webkit-animation: loop 20s infinite;
}
@keyframes "loop"{
    0%{background-image: url('1.jpg')}
    25%{background-image: url('2.jpg')}
    50%{background-image: url('3.jpg')}
    75%{background-image: url('4.jpg')}
    100%{background-image: url('5.jpg')}
}
@-webkit-keyframes "loop"{
    0%{background-image: url('1.jpg')}
    25%{background-image: url('2.jpg')}
    50%{background-image: url('3.jpg')}
    75%{background-image: url('4.jpg')}
    100%{background-image: url('5.jpg')}
}
</style>
<div class="ani"></div>
复制代码

伪类伪元素

写一个表格,使表格奇数行为红色背景,偶数行为黄色背景,鼠标移过去为橘色背景

<style>
	#table tr:nth-child(odd){
		background: red;
	}
	#table tr:nth-child(even){
		background: yellow;
	}
	#table tr:hover{
		background: orange
	}
</style>
<table id="table">
	<tr><td>1</td></tr>
	<tr><td>2</td></tr>
	<tr><td>3</td></tr>
	<tr><td>4</td></tr>
	<tr><td>5</td></tr>
</table>
复制代码

兼容性问题

移动端适配1px的问题

  • 伪类+transform实现
.scale-1px{
    position:relative;
    border:none;
}
// 下边框
.scale-1px:after{
    content:'';
    position:absolute;
    left:0;
    bottom:0;
    background:#000;
    width:100%;
    height:1px;
    -webkit-tranform:scaleY(0.5);
    transform:scaleY(0.5);
    -webkit-transform-origin:0 0;
    transform-origin:0 0;
}
复制代码

position:fixed;在android下无效怎么处理?

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>

DOM

选择器(改变页面内容)

获取全部的checkbox

<input type="checkbox" />
<script type="text/javascript">
var arr=[]''
var inputs = document.getElementsByName('input');
for(var i=0; i<inputs.length;i++){
    if(inputs[i].type="checkbox"){
        arr.push(inputs[i])
    }
}
</script>
复制代码

找到html中id相同的元素

使用document.querySelectorAll('#id')

已知id的input输入框,如何获取输入框输入值?

var inp = document.getElementById('id');
console.log(inp.value)
复制代码

设置已知id的div的html内容为xxxx,字体颜色设为黑色

var ele=document.getElementById('id');
ele.innerHTML="xxxx"
ele.style.color="#000"
复制代码

遍历DOM树

<div class="root">
  <div class="container">
    <section class="sidebar">
      <ul class="menu"></ul>
    </section>
    <section class="main">
      <article id="post"></article>
      <p class="copyright"></p>
    </section>
  </div>
</div>
<script type="text/javascript">
var node = document.getElementsByClassName('root')[0];
var arr=[showNode(node)];
function mapNode(node){
  var nodeList=node.children;
  // console.log(node,nodeList)
  for(var i=0;i<nodeList.length;i++){
    var childNode=nodeList[i];
    console.log(childNode)
    arr.push(showNode(childNode))
    if(childNode.children.length>0){
      mapNode(childNode)
    }
  }
  return arr;
}
function showNode(node){
  if(node.className){
    return node.nodeName+" ."+node.className
  }else{
    return node.nodeName+" #"+node.id;
  }
}
</script>
复制代码

事件

动态根据下拉列表的选项变化,更新图片显示

<select name="changeImg" id="select">
    <option value="1.jpg">1</option>
    <option value="2.jpg">2</option>
    <option value="3.jpg">3</option>
</select>
<script>
var select=document.getElementById('select')
var img=document.getElementById('img')
img.src=select.value;
select.onchange=function(){
    img.src=select.value;
}
</script>
复制代码

给按钮添加事件,确认用户是否退出当前页面,确认以后关闭窗口

考察点:window方法

<button onclick="closeWin()">关闭</button>
<script>
function closeWin(){
    if(confirm('是否退出当前页面?'){
        window.close()
    }
}
</script>
复制代码

现有一个效果,有显示用户头像,用户昵称,用户其余信息;当用户鼠标移到头像上,会弹出用户的全部信息;如何实现此功能?

考察点:事件类型 鼠标的移入移出: mouseover和mouseout(会冒泡);mouseenter和mouseleave(不冒泡)

编写通用的事件监听函数

考察:对事件的了解

var EventUtil={
    addEvent:function(element,type,handler){
        if(element.addEventListener){
            element.addEventListener(type,handler)
        }else if(element.attachEvent){
            element.attachEvent('on'+type,handler)
        }else{
            element['on'+type]=handler
        }
    },
    removeEvent:function(element,type,handler){
        if(element.removeEventListener){
            element.removeEventListener(type,handler)
        }else if(element.detachEvent){
            element.detachEvent('on'+type,handler)
        }else{
            element['on'+type]=null
        }
    },
    getEvent:function(event){
        return event||window.event
    },
    getTarget:function(event){
        return event.target||event.srcElement
    },
    preventDefault:function(event){
        if(event.preventDefault){
            event.preventDefault()
        }else{
            event.returnValue=false
        }
    },
    stopPropagation:function(event){
        if(event.stopPropagation){
            event.stopPropagation()
        }else{
            event.cancelBubble=true
        }
    }
}
复制代码

设计基于观察者模式的事件绑定机制

观察者模式简单说就是当一个对象被修改时,会自动通知它的依赖对象,发布-订阅模式。

var Event={
    // 经过on接口监听事件eventName
    // on至关于订阅
    on: function(eventName, handler){
        if(!this.handler){
            this.handler={}
        }
        if(!this.handler[eventName]){
            this.handler[eventName]=[]
        }
        this.handler[eventName].push(handler)
    },
    // 触发事件eventName
    // emit至关于发布
    emit: function(eventName){
        // 若存在eventName,则调用全部事件处理函数
        if(this.handler[eventName]){
            for(var i=0;i<this.handler[eventName].length;i++){
                this.handler[eventName][i](arguments[1])
            }
        }
    }
}
Event.on('test', function(a){
    console.log(a)
})
Event.emit('test',78) //78
Event.on("test", function(a){
    console.log("个人数字是"+a)
});
Event.emit("test", 88);
// 88
//个人数字是88
复制代码

存储

请实现tip组件功能,描述:1.用户第一次进来显示,同一天访问该页面不显示 2.用户点击我知道了而后访问此页面再也不显示tip?

<div id="tip">提示</div>
<div id="know">我知道了</div>
<script type="text/javascript">
var tip=document.getElementById('tip');
var storage=window.localStorage;
// 存储的日期
var date=storage.getItem('date');
// 用户是否点击过我知道了
var ifKnow=storage.getItem('know');
// 今天日期
var today=new Date();
today=today.getFullYear()+'-'+today.getMonth()+'-'+today.getDate();
// 点击我知道了再也不显示
var know=document.getElementById('know');
know.onclick=function(){
  storage.setItem('know', true)
  tip.style.display="none";
}
// 若是点击过我知道了,则不显示
if(ifKnow){
  tip.style.display="none";
}else{
  if(date!=null){
    if(date==today){
      // 今日已登陆过,不显示
      tip.style.display="none"
    }else{
      // 今日还没有登陆过
      storage.setItem('date',today)
    }
  }else{
    // 第一次访问网站,尚未存储日期
    storage.setItem('date', today)
  }
}
</script>
复制代码

ECMAScript

数据类型和变量提高

看代码输出结果,解释缘由

var a;
alert(typeof a);    
alert(b);          
b=10;
alert(typeof b);    
复制代码

a使用了var声明变量未赋值因此为undefined,b因为未声明将报错。 若b=10改成var b=0;则返回undefined由于var会提高声明。

看代码输出结果,解释缘由

var foo=1;
function test(){
    console.log(foo); 
    var foo=2;
    console.log(foo); 
}
test();
复制代码

缘由:函数内部var声明会提高到函数内部顶部,因此就至关于

var foo=1;
function test(){
    var foo;
    console.log(foo);
    foo=2;
    console.log(foo)
}
复制代码

回答输出结果

if(!("a" in window)){
    var a=1
}
console.log(a) 
复制代码

由于var存在声明提高,因此"a" in window为true,就不执行if语句中的赋值了,因而为undefined

回答输出结果

var foo=1;
function bar(){
    if(!foo){
        var foo=10
    }
    console.log(foo)
}
bar(); // 10
复制代码

var存在声明提高,因此if(!foo)中的foo为undefined,会被执行,结果为10

回答输出结果

var a=10,b=11,c=12;
function test(a){
    a=1;
    var b=2;
    c=3;
}
test(14);
console.log(a); // 10
console.log(b); // 11
console.log(c); // 3
复制代码

我的认为,test(a)传入的参数a,会被解析成var a=14,因此a也是局部变量

回答输出结果

function fn(){
    f=ff()
    return f;
    function ff(){
        return 'f' in window;
    }
    var f;
}
console.log(fn()); 
复制代码

函数会被整个提高到最上方,因此ff()中会执行,但因为函数做用域,window下没有f属性,返回false;

回答输出结果

console.log(a); 
var a=13;
function fn(){
    console.log(a); 
    var a=1;
}
fn(); 
console.log(a); 
复制代码

输出undefined,undefined,13。缘由:js引擎会先把var声明提到顶部,而后再把函数声明整个函数提到var的前面,以上代码等于

function fn(){
    var a;
    console.log(a);
    a=1;
}
var a;
console.log(a);
a=13;
fn();
console.log(a)
复制代码

回答输出结果

console.log(a); 
var a=13;
function fn(){
    console.log(a); 
    a=1;
}
fn()
console.log(a) 
复制代码

输出undefined,13,1。由于fn中的a=1没有var,因此不存在变量提高,因此fn()中console.log(a)能够访问到外部的,等fn()以后,a=1没有var,不是全局变量改变了原先a的值,因此最后一个输出1

console.log(a); 
a=13;
function fn(){
    console.log(a);
    a=1;
}
fn()
console.log(a)
复制代码

报错,13,1

引用类型

回答代码输出结果,解释缘由

var a=new Object();
a.value=1;
b=a;
b.value=2
console.log(a.value)
复制代码

对象参数是按引用传递的

var a=[1];
var b=a;
b=[2];
console.log(a);
复制代码

b=[2]指向了其余的地址,不是原引用了

回答代码输出结果,解释缘由

function changeObj(o){
    o.name="first";
    o = new Object();
    o.name="second";
}
var obj=new Object();
changeObj(obj);
console.log(obj.name);
复制代码

o=new Object()切断了原先的引用,此时obj.name还是指向原来的,新的就只是一个另外的对象

回答代码输出结果

var a=b=c=[1,2,3,4];
b=9;
a[1]=0;
console.log(b[0]) 
a=[1,2,3,4]
c=[1,2,3,4]
a[0]=b;
console.log(c);
console.log(a);
复制代码

b=9时已经被切断引用了,a,c也是被切断了,a[0]=b=9,因此a=[9,2,3,4]

实现一个函数clone,能够对JavaScript中的5种主要数据类型(Number,String,Object,Array,Boolean)进行值复制?

function clone(obj){
    var o;
    switch (typeof obj){
        case "undefined":
            break;
        case "string":
            o=obj+"";
            break;
        case "number":
            o=obj-0;
            break;
        case "boolean":
            o=obj;
            break;
        case "object":
            if(obj===null){
                o=null
            }else{
                if(Object.prototype.toString.call(obj)==="[object Array]"){
                    o=[];
                    for(var i=0;i<obj.length;i++){
                        o.push(clone(obj[i]))
                    }
                }else{
                    o={};
                    for(var k in obj){
                        o[k]=clone(obj[k])
                    }
                }
            }
            break;
        default:
            o=obj;
            break;
    }
    return o;
}
复制代码

运算符

看代码输出结果,解释缘由

function show(){
    var b=1;
    a=++b;
}
show();
alert(a);
复制代码

a为2,++b就是b=b+1而后返回b,b++就是先返回b,而后b=b+1

字符串

已知有字符串"get-element-by-id",将其转换为getElementById

var str='get-element-by-id';
function formatString(str){
  var arr=str.split('-');
  var newStr=arr[0]
  for(var i=1;i<arr.length;i++){
    var first=arr[i].slice(0,1).toUpperCase();
    var other=arr[i].slice(1)
    newStr=newStr+first+other
  }
  return newStr
}
formatString(str)
复制代码

清除字符串先后的空格(兼容全部浏览器)

function myTrim(str){
  if(String.prototype.trim){
    return str.trim()
  }else{
    return str.replace(/^\s+/, "").replace(/\s+$/,"")
  }
}
console.log(myTrim(" hello "))
复制代码

统计一个字符串中出现次数最多的字符和出现次数

function test(str){
  var obj={};
  // 先将出现过的字符和次数记录下来
  for(var i=0; i<str.length;i++){
    var key = str[i]
    if(!obj[key]){
      obj[key]=1
    }else{
      obj[key]++;
    }
  }
  var max=0;
  var idx=0;
  // idx为字符,max为出现次数
  for(var i in obj){
    if(obj[i]>max){
      max=obj[i]
      idx=i;
    }
  }
  console.log(idx,max)
}
var str="abcdefgaddda";
test(str);
复制代码

删除与某个字符相邻且相同的字符,好比fdaffdaaaklfjklja转换成fdafdaklfjklja

function test(str){
  var newStr="";
  for(var i=0;i<str.length;i++){
    if(str.indexOf(str[i])!==i){
      newStr=newStr+""
    }else{
      newStr=newStr+str[i]
    }
  }
  return newStr;
}
复制代码

求一个字符串的字节长度

假设中文字符占两个字节,英文字符占一个字节

function getByteLen(str){
  var reg=/[\u4e00-\u9fa5]/;
  var len=0;
  for(var i=0;i<str.length;i++){
    if(reg.test(str.charAt(i))){
      len+=2
    }else{
      len++
    }
  }
  return len;
}
复制代码

将url的各个参数提取出来,按key-value形式返回一个对象

var url='http://test.com?a=1&b=2&cd=344'
function formatUrl(url){
  var urlArr=url.split('?');
  url=urlArr[1];
  var arr=url.split('&')
  var obj={}
  for(var i=0;i<arr.length;i++){
    var res=arr[i].split('=');
    var key=res[0];
    var val=res[1];
    obj[key]=val;
  }
  return obj;
}
formatUrl(url)
复制代码

正则

如何将浮点数左边的数每三位添加一个逗号,如12000000.11转换为12,000,000.11?

function formatNum(num){
  var newStr="";
  var count=0; 
  var str=num.toString();
  for(var i=str.indexOf('.')-1;i>=0;i--){
    if(count%3==0 && count!=0){
      newStr=str.charAt(i)+','+newStr;
    }else{
      newStr=str.charAt(i)+newStr;
    }
    count++;
  }
  str=newStr+str.substr((str).indexOf('.'));
  return str;
}
formatNum(123456566.222)
复制代码

匹配邮箱和手机号

function phone(str){
  var reg=/^1[3|4|5|7|8|9]\d{9}$/;
  if(reg.test(str)){
    console.log('yes')
  }else{
    console.log('no')
  }
}
function email(str){
  var reg=/^[0-9a-zA-Z_\.\-]+\@+[0-9a-zA-Z_\.\-]+\.(com|com.cn|edn|hk|cn|net)$/;
  if(reg.test(str)){
    console.log('yes')
  }else{
    console.log('no')
  }
}
复制代码

去除字符串中HTML标签

function replace(str){
  var newStr=str.replace(/<\/?\w+>/gi, "")
  console.log(newStr)
}
复制代码

将<,>,&,"进行转义

function test(str){
  return str.replace(/[<>"&]/g, function(match){ switch(match){ case "<": return '&lt;' case ">": return "&gt;" case "&": return "&amp;" case "\"":
        return "&quot";
    }
  })
}
复制代码

将字符串"<tr><td>{$id}</td><td>{$name}</td></tr>"中的{$id}替换成10,${name}替换成chen

function test(str){
  return str.replace(/{\$id}/g,'10').replace(/{\$name}/g, "chen")
}
复制代码

匹配输入的字符:第一个必须是字母或下划线开头,后面就是字母和数字或者下划线构成,长度5-20

function testStr(str){
  var reg=/^[a-zA-Z_]+[0-9a-zA-Z_]{5,20}/;
  if(reg.test(str)){
    console.log('yes')
  }else{
    console.log('no')
  }
}
复制代码

假设有一篇文章var content="....",涉及到一些敏感词["死了","20","放屁"]等,如何在文章中发现这些敏感词,并将背景置为红色或改变字体颜色标识出来。

<div id="content">
我真的要丢脸死了,我今天在课堂上放屁了并且很大声,这是我18年来最丢脸的一次。
</div>
<script type="text/javascript">
function testStr(id){
  var node=document.getElementById(id);
  var content=node.innerHTML;
  var reg=/死了|18|放屁/g;
  var res=content.replace(reg, function(match){
    return "<span style='color:red'>"+match+"</span>"
  })
  node.innerHTML=res;
}
testStr('content')
</script>
复制代码

Math对象

获取一个长度一致的随机的字符串

// 获取随机两位整数
var random=Math.random();
random=Math.floor(random*100);

// 获取随机三位小数
var random=Math.random()+'00000';
random=random.slice(0,5);
复制代码

如何实现数组的随机排序?

// 方法1
function randomArr(arr){
  for(var i=0;i<arr.length;i++){
    var random=Math.floor(Math.random()*arr.length);
    var val=arr[random];
    arr[random]=arr[i];
    arr[i]=val;
  }
  return arr;
}
// 方法2
arr.sort(function(){
    return Math.random-0.5;
})
复制代码

实现随机10-100之间的10个数字,存入一个数组并排序

function randomArr(){
  var arr=[]
  for(var i=0;i<5;i++){
    var random=Math.floor(Math.random()*(100-10)+10);
    arr.push(random)
  }
  return arr.sort();
}
复制代码

数组

实现对数组的倒序

// 方法1
arr.reverse()
// 方法2
function test(arr){
    var newArr=[];
    for(var i=0;i<arr.length;i++){
        newArr.unshift(arr[i])
    }
}
复制代码

实现数组的降序排列

// 方法1
function test(arr){
    arr.sort(function(x,y){
        return y-x;
    })
}
// 方法2
var arr=[1,2,34,40,56]
function test(arr){
  for(var i=0;i<arr.length-1;i++){
    for(var j=0;j<arr.length-1-i;j++){
      if(arr[j]<arr[j+1]){
        var val=arr[j]
        arr[j]=arr[j+1];
        arr[j+1]=val
      }
    }
  }
  return arr;
}
复制代码

数组去重

// 方法1 利用indexOf
function test(arr){
  var newArr=[];
  for(var i=0;i<arr.length;i++){
    if(arr.indexOf(arr[i])===i){
      newArr.push(arr[i])
    }
  }
  console.log(newArr)
}
// 方法2 利用indexOf
function test1(arr){
  var newArr=[];
  for(var i=0;i<arr.length;i++){
    if(newArr.indexOf(arr[i])===-1){
      newArr.push(arr[i])
    }
  }
  console.log(newArr)
}
// 方法3 利用splice和indexOf
function test(arr){
  var newArr=[];
  for(var i=0;i<arr.length;i++){
    if(arr.indexOf(arr[i])!==i){
      arr.splice(i,1);
      i--;
    }
  }
  console.log(arr)
}
// 方法4 利用ES6的set
function test1(arr){
  return Array.from(new Set(arr))
}
复制代码

找到数组arr中重复出现过的元素(若给出多种方式,请分别给出它们的复杂度)

// 方法1
var arr=[5,1,18,4,1,3,4,3];
function test(arr){
  var newArr=[];
  for(var i=0;i<arr.length;i++){
    for(var j=i+1;j<arr.length;j++){
      if(arr[i]===arr[j] && newArr.indexOf(arr[i])==-1){
        newArr.push(arr[i])
      }
    }
  }
  console.log(newArr)
}
复制代码

不用循环,建立一个长度为100的数组,而且每一个元素的值等于它的下标

// 方法1
[...new Array(100).keys()]

// 方法2
Array.from(new Array(100),(item,idx)=>idx)
复制代码

实现一个函数,函数参数是一个数组,返回一个新数组,新数组的每一项是原数组的值的两倍,如[2,4,6]返回[4,8,12]

function test(arr){
  return arr.map(function(item,index,array){
    return item*2;
  })
}
function test1(arr){
  var newArr=[];
  arr.forEach(function(item,index,array){
    newArr.push(item*2)
  })
  return newArr;
}
function test2(arr){
  for(var i=0;i<arr.length;i++){
    arr[i]=arr[i]*2;
  }
  return arr;
}
复制代码

阅读代码,分析结果

var arr=new Array(1,3,5);
arr[4] = 'z';
arr2=arr.reverse();
arr3=arr.concat(arr2);
console.log(arr3)
复制代码

["z", undefined, 5, 3, 1, "z", undefined, 5, 3, 1] 由于reverse()会改变原数组

写一个能遍历对象和数组的通用forEach函数

function forEach(obj,fn){
    // 判断是否为数组
    if(obj instanceof Array){
        obj.forEach(function(item,index){
            fn(index,item)  
        })
    }else{
        for(var key in obj){
            fn(key, obj[key])
        }
    }
}
复制代码

判断一个给定10位字符串是否为合法的日期,例如:'2016-10-31'是一个合法日期则返回true,'2016-10-31'不是一个合法日期,则返回false

function test(str){
  var reg=str.match(/^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2})$/);
  if(!reg){
    console.log('日期格式错误');
    return;
  }
  var d=str.split('-');
  var year=parseInt(d[0]);
  var month=parseInt(d[1]);
  var date=parseInt(d[2]);
  if(year<=0){
    console.log('日期错误')
    return;
  }
  if(month>12||month<=0){
    console.log('日期错误')
    return;
  }
  if((year%4===0&&year%100!==0)||year%400===0||year%500==0){
    // 为闰年
    if(month==2&&(date>29||date<=0)){
      console.log('日期错误')
      return;
    }
  }else{
    if(month==2&&(date>28||date<=0)){
      console.log('日期错误')
      return;
    }
  }
  if(month==4||month==6||month==9||month==11){
    if(date>30||date<=0){
      console.log('日期错误')
      return;
    }
  }else{
    if(date>31||date<=0){
      console.log('日期错误')
      return;
    }
  }
  return str;
}
复制代码

Function

回答输出结果

// 第二步执行
var test=(function(a){
    // a=1,可是b尚未定义由于函数做用域
    this.a=a;
    return function(b){
        // 第三步执行,接收test(4)的参数,因此b=4,this.=1
        return this.a+b;
    }
}
// 第一步执行
(function(a,b){
    // a=1,b=2
    return a;
}(1,2))
)
console.log(test(4))
复制代码

返回5

回答代码输出结果

function foo(){
    foo.a=function(){
        console.log(1);
    }
    this.a=function(){
        console.log(2)
    }
    a=function(){
        console.log(3)
    }
    var a=function(){
        console.log(4)
    }
}
foo.prototype.a=function(){console.log(5)}
foo.a=function(){console.log(6)}
// 此时foo还没真正执行,因此只有上面这个属性a,因此返回6
foo.a();  // 执行了foo()
var obj=new foo();
// new方法调用以后this就是指向obj的,因此obj.a就至关于this.a
obj.a()
// 上面执行了foo()以后,foo.a改变了,因此输出1
foo.a()
复制代码

6,2,1

输出结果

var a=5
function test(){
    a=0;
    console.log(a)
    console.log(this.a);
    var a;
    console.log(a);
}
test();
new test();
复制代码

第一个test()是直接调用的因此this指向window,第二个new test()中this会被绑定一个空对象上,因此this.a为undefined

输出结果:

var myObj={
    foo: 'bar',
    func: function(){
        var self=this;
        console.log(this.foo)
        console.log(self.foo)
        (function(){
            console.log(this.foo)
            console.log(self.foo)
        }())
    }
}
myObj.func()
复制代码

bar,bar,undefined,bar

myObj.func()因此this绑定在myObj上,而后在func()里的函数this就会指向func,因此返回undefined。

关于this绑定不太清楚的,能够看木易杨说的JavaScript深刻之史上最全--5种this绑定全面解析

定义一个log方法,让它代理console.log的方法

var log=console.log.bind(console)
复制代码

arguments

写一个函数能够计算sum(5,0,-5)输出0;sum(1,2,3,4)输出10;

能够用...或arguments获取参数

function sum(...arr){
    var sum=0;
    arr.forEach(function(item){
        sum=sum+item;
    })
    return sum;
}
复制代码

只列举一种,方法有不少。

闭包

回答代码输出结果,解释缘由

var foo="hello"
(function(){
    var bar="world"
    console.log(foo+bar)
})()
console.log(foo+bar)
复制代码

"helloworld", 报错,缘由:函数做用域

输出结果

var z=10;
function foo(){
    console.log(z);
}
(function(funArg){
    var z=20;
    funArg()
})(foo);
复制代码

函数自动执行,函数参数是值传递,funArg引用外部foo函数,而foo函数做用域没有z变量,因此找到全局变量z,输出结果10

输出结果:

(function(){
    var a=b=3
})();
console.log("a defined?"+(typeof a !== 'undefined'))
console.log("b defined?"+(typeof b !== 'undefined'))
复制代码

var a=b=3能够解析出var a=b,b=3;因此b是全局变量,因此typeof b为number,typeof对于不存在的变量a返回undefined

输出结果

function fun(n, o){
    console.log(o)
    return{
        fun: function(m){
            return fun(m,n)
        }
    }
}
var a=fun(0); a.fun(1); a.fun(2); a.fun(3);
var b=fun(0).fun(1).fun(2).fun(3);
var c=fun(0).fun(1); c.fun(2); c.fun(3);
复制代码

这里是一个闭包的概念,保存变量的值 第一行

var a=fun(0);  // 这里o是undefined,n=0,会保存在a中
a.fun(1);   // m=1,n=0 => n=1,o=0
a.fun(2);   // m=2,n=0 => n=2,o=0
a.fun(3);   // m=3,n=0 => n=3,o=0
复制代码

从第二行开始,var b=fun(0).fun(1).fun(2).fun(3),能够转化为

var b=fun(0);       // 在这里面o是undefined,n=0会保存在b中
var b1=b.fun(1);    // o=undefined,m=1,n=0 => n=1,o=0保存到b1中
var b2=b1.fun(2);   // o=0,m=2,n=1 => n=2,o=1 保存到b2中
var b3=b2.fun(3);   // o=1,m=3,n=2 => n=3,o=2
复制代码

第三行,转化为

var c=fun(0);       // 在这里面o是undefined,n=0会保存在c中
var c1=c.fun(1);    // o=undefined,m=1,n=0 => n=1,o=0保存到c1中
c1.fun(2);          // o=0,m=2,n=1 => n=2,o=1
c1.fun(3);          // o=0,m=3,n=1 => n=3,o=1
复制代码

回答输出结果,若是不正确如何修改

for(var i=0;i<5;i++){
    setTimeout(function(){
        console.log(i)
    },100*i)
}
复制代码

会输出5个5

// 方法1
for(let i=0;i<5;i++){
    setTimeout(function(){
        console.log(i)
    },100*i)
}
// 方法2
for(let i=0;i<5;i++){
    (function(i){
        setTimeout(function(){
            console.log(i)
        },100*i)
    })(i)
}
复制代码

写一段代码实现tab的切换

考察点:闭包

<style>
.hidden{
    display: none;
}
</style>
<ul id="tabs">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<div id="content">
    <div>第一</div>
    <div>第二</div>
    <div>第三</div>
</div>
<script>
var tabs=document.getElementById('tabs');
var tabList=tabs.children;
var content=document.getElementById('content');
var conList=content.children;
// 方式1
for(var i=0;i<tabList.length;i++){
    tabList[i].index=i;
    tabList[i].onclick=function(){
        for(var j=0;j<conList.length;j++){
            conList[j].style.display="none"
        }
        conList[this.index].style.display="block"
    }
}
// 方式2 用闭包
for(var i=0;i<tabList.length;i++){
    tabList[i].onclick=function(i){
        return function(){
            for(var j=0;j<conList.length;j++){
                conList[j].style.display="none"
            }
            conList[i].style.display="block"
        }
    }(i)
}
</script>
复制代码

建立10个a标签点击时弹出对应的序号

// 1
for(let i=0;i<10;i++){
    var a=document.createElement('a');
    a.innerHTML=i+"<br>";
    a.onclick=function(){
        console.log(i)
    }
    document.body.appendChild(a)
}
// 2
for(var i=0;i<10;i++){
    (function(i){
        var a=document.createElement('a');
        a.innerHTML=i+"<br>";
        a.onclick=function(){
            console.log(i)
        }
        document.body.appendChild(a)
    })(i)
}
复制代码

异步

输出结果:

(function(){
    console.log(1);
    setTimeout(function(){
        console.log(2)
    },1000)
    setTimeout(function(){
        console.log(3)
    },0);
    console.log(4)
})()
复制代码

1,4,3,2 缘由:会先将同步代码执行完毕,再执行异步代码

看代码输出结果,解释缘由

var a=6
setTimeout(function(){
    console.log(a);
    a=666
}, 1000)
a=66;
复制代码

66 缘由:会先执行同步代码,再执行异步代码

EventLoop

看代码输出结果,解释缘由

//请写出输出内容
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
	console.log('async2');
}

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end')
复制代码
  • JS分为同步任务和异步任务,同步任务在主线程上,主线程外有一个任务队列,一旦同步任务执行完毕就会读取任务队列将可运行的异步任务添加到执行栈中,开始执行。
  • EventLoop中能够有一个或多个任务队列,任务队列是一系列有序任务task的集合,每一个任务都有一个任务源。不一样任务源会被分配到不一样task队列中,任务源分为微任务microtask和宏任务macrotask。
  • 宏任务包含script(总体代码),setTimeout,setInterval,I/O,UI交互时间,postMessage,MessageChannel,setImmediate
  • 微任务包含promise,MutationObserver,process.nextTick
  • EventLoop运行机制 1.执行一个宏任务 2.执行过程若是遇到微任务就添加到微任务的任务队列中 3.宏任务执行完毕后,当即执行当前微任务队列中的全部微任务(依次执行) 4.当前宏任务执行完毕,开始检查渲染,而后GUI线程接管渲染 5.渲染完毕后,JS线程继续接管,开始下一个宏任务
  • 关于Promise 写在Promise中的代码是被当作同步任务当即执行,而then和catch是异步的
  • 关于await await是一个让出线程的标志,await后面的代码会先执行一遍,将await后面的代码加入microtask,而后就会跳出整个async函数来执行后面的代码
async function a1(){
    console.log('a1 start')
    await a2() // 执行,执行完毕才会回来执行a1 end
    cosnole.log('a1 end') // 会被注册为microtask
}
复制代码

实现一个简易版Promise

const PENDING='pending'
const RESOLVED='resolved'
const REJECTED='rejected'
function myPromise(fn){
    const that=this;
    that.state=PENDING; // Promise当前状态
    that.value=null;    // Promise的值
    that.resolvedCallbacks=[]
    that.rejectedCallbacks=[]
    function resolve(value){
        if(value instancof myPromise){
            return value.then(resolve, reject)
        }
        setTimeout(() => {
            if(that.state===PENDING){
                that.state===RESOLVED;
                that.value===value
                // 依次执行成功以后的函数栈
                that.resolvedCallbacks.map(cb => cb(that.value))
            }
        },0)
    }
    function reject(error){
        setTimeout(() => {
            if(that.state===PENDING){
                that.state===REJECTED
                that.value=error;
                // 依次执行失败以后的函数栈
                that.rejectedCallbacks.map(cb => cb(that.error))
            }
        },0)
    }
    try{
        fn(resolve, reject)
    }catch(e){
        reject(e)
    }
}
myPromise.prototype.then = function(onFulfilled, onjected){
    const that=this;
    onFulfilled=typeof onFulfilled === 'function' ? onFulfilled: v => v;
    onRejected=typeof onRejected === 'function' ? onRejected : r => {throw r}
    if(that.state === PENDING){
        that.resolvedCallbacks.push(onFulfilled)
        that.rejectedCallback.push(onRejected)
    }
    if(that.state === RESOLVED){
        onFulfilled(that.value)
    }
    if(that.state === REJECTED){
        onRejected(that.value)
    }
}
new myPromise((resolve,reject) => {
    setTimeout(() => {
        resolve(1)
    }, 0)
}).then(value => {
    console.log(value)
})
复制代码

关于优化

关于图片优化

  • 对于修饰图片能够用CSS替代
  • 对于移动端没必要加载原图,可使用CDN加载,计算出适配屏幕的宽度,而后请求相应裁剪好的图片
  • 小图使用base64格式
  • 将多个图标文件整合到一张图片中(雪碧图)
  • 选择正确的图片格式,小图使用png,图标可使用svg替代,照片使用jpeg

怎么控制一次加载一张图片,加载完后再加载下一张?

监控图片是否加载完成,加载完再加载下一张

<img src="">
<img src="">
<img src="">
<img src="">
var imgList=document.getElementsByTagName('img');
var urlList=[
  'http://pica.nipic.com/2007-07-15/200771515512480_2.jpg',
  'http://www.zyzw.com/sjms/sjmst/sjmsdg021.jpg',
  'http://pic11.nipic.com/20100803/4038389_093502059852_2.jpg',
  'https://img03.sogoucdn.com/app/a/100520093/ac75323d6b6de243-6a8470ba18ff4002-5f37053dc99bdc42f6f306e09de5e133.jpg'
]
// 加载图片
function loadImg(i){
  var obj=new Image();
  obj.src=urlList[i];
  obj.onload=function(){
    imgList[i].src=urlList[i];
    i=i+1;
    if(i<imgList.length){
      loadImg(i)
    }
  }
}
loadImg(0);
复制代码

图片懒加载和预加载

  • 预加载:提早加载图片,当用户须要查看时可直接从本地缓存中渲染
  • 懒加载:主要是为了减小请求数或延迟请求数,不在可视区域的图片先不加载。

节流与防抖

  • 防抖:防止手抖,短期内连续点击只会执行一次
// func是用户传入须要防抖的函数
// wait是等待时间
const debounce = (func, wait=50) => {
    // 缓存一个定时器id
    let timer=0
    // 返回的参数就是每次用户实际调用的防抖函数
    return function(...args){
        // 清空上一次的定时器
        if(timer) clearTimeout(timer)
        timer=setTimeout(() => {
            func.apply(this,args)
        }, wait)
    }
}
function sayHi(){
    console.log('防抖成功')
}
var inp=document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi, 500));
复制代码
  • 节流:高频事件触发,只会在n秒内执行一次,稀释函数的执行频率(防抖是将屡次执行变为最后一次执行,节流是将屡次执行变成每隔一段时间执行)
const throttle(fn, wait=500) => {
    let canRun=true;
    return function(...args){
        if(!canRun) return;
        canRun=false;
        setTimeout(()=>{
            fn.apply(this, args)
            canRun=true
        }, wait)
    }
}
function resizeWindow(e){
    console.log(e.target.innerWidth, e.target.innerHeight)
}
window.addEventListener('resize', throttle(resizeWindow))
复制代码

算法

已知以下数组:

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 编写一个程序将数组扁平化去并除其中重复部分数据,最终获得一个升序且不重复的数组

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
// 方法1
// 扁平化数组
function flat(arr){
  var newArr=[];
  arr.forEach(function(item){
    if(Array.isArray(item)){
      newArr=newArr.concat(flat(item))
    }else{
      newArr.push(item)
    }
  })
  return newArr;
}
// 去重和排序
function  formatArr(arr) {
  var newArr=flat(arr);
  newArr.sort(function(a,b){
    return a-b;
  });
  for(var i=0;i<newArr.length;i++){
    if(newArr.indexOf(newArr[i])!==i){
      newArr.splice(i,1)
    }
  }
  return newArr;
}
formatArr(arr);

// 方法2
function flat(arr){
    arr=arr.toString().split(',') // 扁平化数组
    arr=arr.sort(function(a,b){
        return a-b;
    })
    arr=arr.map(Number) // 逐个将每一个元素转为数字类型
    arr=Array.from(new Set(arr)) // 数组去重
    return arr;
}
复制代码

几种排序算法的实现

冒泡排序

相邻的两个数进行比较,若是前者大于后者就交换位置,这样一来,第一轮就能够选出一个最大的数放在最后面;那么通过n-1轮,就会完成全部数的排序。

function bubbleSort(arr){
    for(var i=0;i<arr.length-1;i++){
        for(var j=0;j<arr.length-1-i;j++){
            if(arr[j]>arr[j+1]){
                var temp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=temp;
            }
        }
    }
    return arr;
}
复制代码

选择排序

  • 在未排序的数组中找到最小元素,存放到数组起始位置
  • 从剩余未排序元素中继续寻找最小元素,放到已排序序列的末尾
  • 重复第二步,直到全部元素排序完毕 好处:不占用额外的内存空间,坏处:时间复杂度为O(n^2),因此数据规模越小越好
function selectSort(arr){
  for(var i=0; i<arr.length-1;i++){
    var minIdx=i;
    // 找到未排序序列中的最小值
    for(var j=i+1;j<arr.length;j++){
      if(arr[j]<arr[minIdx]){
        minIdx=j;
      }
    }
    // 将最小值放到已排序序列的后面
    var temp=arr[i];
    arr[i]=arr[minIdx];
    arr[minIdx]=temp;
  }
  return arr;
}
复制代码

插入排序

  • 插入排序跟整理扑克牌是同样的,即每次拿到一个数,按大小顺序将其插入到有序的数组中。
  • 首先初始化第一个元素为有序数列,取出一个无序数列中的元素按大小顺序插入有序数组中。
function insertSort(arr){
  for(var i=1;i<arr.length;i++){
    // 将要插入的数
    let temp=arr[i];
    // 有序数列
    for(var j=i-1;j>=0;j--){
      // 要插入的数与有序数列一一比较
      if(temp<arr[j]){
        arr[i]=arr[j]
        arr[j]=temp;
      }
    }
  }
  return arr;
}
复制代码

希尔排序

  • 希尔排序也叫递减增量排序算法,是插入排序的升级版。
  • 先将无序数组分割成若干子序列,子序列是相隔特定增量的子序列,对每一个子序列进行插入排序
  • 而后再选择一个更小的增量,再将以前排序后的数组按这个增量分割成多个子序列
  • 不断选择更小的增量,直到增量为1时,再对序列进行一次插入排序,使序列最终成为有序序列
function shellSort(arr){
  var gap=Math.floor(arr.length/2);
  while(gap>0){
    for(var i=gap; i<arr.length;i++){
      var temp=arr[i];
      for(var j=i; j-gap>=0 && arr[j-gap]>temp; j=j-gap){
        arr[j]=arr[j-gap];
      }
      arr[j]=temp;
    }
    gap=Math.floor(gap/2);
  }
  return arr;
}
复制代码

归并排序

先递归分解数列再合并数列,将一个数组拆分红A,B两个小组,一直拆到每一个小组只有一个元素为止。 看小册

快速排序

  • 先取出一个数做为基准
  • 而后把大于这个数的放到它右边,小于等于这个数的放到左边
  • 再对左右区间重复第二步,直到各区间只有一个数
function quickSort(arr){
    if(arr.length<=1){
        return arr;
    }
    // 基准位置(理论上可任意选取)
    var idx=arr.length-1;
    // 基准值
    var num=arr[idx];
    var left=[];
    var right=[];
    for(var i=0;i<arr.length-1;i++){
        if(arr[i]<=num){
            left.push(arr[i])
        }else{
            right.push(arr[i])
        }
    }
    // 会先将左边的排序好再开始对右边进行排序
    return quickSort(left).concat([num],quickSort(right));
}
复制代码
相关文章
相关标签/搜索