前端面试题(html+css+js)

一、HTML 基础篇
1、doctype 的作用是什么?

DOCTYPE 是 html5 标准网页声明,且必须声明在HTML文档的第一行。来告知浏览器的解析器用什么文档标准解析这个文档,不同的渲染模式会影响到浏览器对于 CSS 代码甚至 JavaScript 脚本的解析。

2、HTML、XHTML、XML 有什么区别?

HTML(超文本标记语言): 在 html4.0 之前 HTML 先有实现再有标准,导致 HTML 非常混乱和松散
XML(可扩展标记语言): 主要用于存储数据和结构,JSON作用类似,但更加轻量高效
XHTML(可扩展超文本标记语言): 基于上面两者而来

3、HTML 语义化的理解?

语义化:指使用恰当语义的 html 标签,如 header 标签 代表头部,article 标签代表正文等
好处:增强了可读性、有利于SEO优化

4、常用的 meta 标签?

charset,用于描述 HTML 文档的编码形式

<meta charset="UTF-8" >

http-equiv,相当于http 的文件头作用,比如下面的代码就可以设置 http 的缓存过期日期

<meta http-equiv="expires" content="Wed, 20 Jun 2019 22:33:00 GMT">

viewport,控制视口的大小和比例

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
5、script 标签中 defer 和 async 的区别?

defer:script 被异步加载后并不会立刻执行,而是等待文档被解析完毕后执行。
async:脚本加载完毕后立即执行

6、前端储存的方式?

cookies: 兼容性好,请求头自带 cookie 方便,缺点是大小只有4k,自动请求头加入 cookie 浪费流量,每个 domain 限制20个 cookie,使用起来麻烦需要自行封装
localStorage:HTML5 加入的以键值对(Key-Value)为标准的方式,优点是操作方便,永久性储存(除非手动删除),大小为5M,兼容IE8+
sessionStorage:与 localStorage 基本类似,区别是 sessionStorage 当页面关闭后会被清理,而且与 cookie、localStorage 不同,他不能在所有同源窗口中共享,是会话级别的储存方式
IndexedDB:NoSQL 数据库,用键值对进行储存,可以进行快速读取操作,非常适合 web 场景,同时用 JavaScript 进行操作会非常方便。

二、CSS 篇
1、CSS 盒模型
标准模型:宽高计算不包含 padding 和 border ;通过 box-sizing: content-box; 来设置(浏览器默认)。
IE模型:宽高计算包含 padding 和 border ;通过 box-sizing: border-box; 来设置。
2、BFC(块状格式化上下文)
特点:

是一个独立的容器,里面的元素和外面的元素互不影响;
BFC垂直方向的边距会发生重叠;
BFC 区域不会与浮动元素区域重叠;
计算 BFC 高度时,浮动元素也参与计算。

创建方式:

float 值不为 none;
position 的值不为 static 或 relative;
display 为 inline-box, table, table-cell 等;
overflow 不为 visible

作用:

清除浮动
防止同一 BFC 容器中的相邻元素间的外边距重叠问题

3、实现垂直居中布局

宽高固定

div.parent {
    position: relative; 
}

div.child {
    width: 100px;
    height: 100px;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -50px;
    margin-top: -50px;
}

div.child {
    width: 100px;
    height: 100px;
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    margin: auto;
}


宽高不固定

div.parent {
    display: flex;
    justify-content: center;
    align-items: center;
}

div.parent{
  display:flex;
}
div.child{
  margin:auto;
}

div.parent {
    position: relative; 
}
div.child {
    position: absolute; 
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);  
}

div.parent {
    display: grid;
}
div.child {
    justify-self: center;
    align-self: center;
}
4、分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景。

结构上:display:none 会从渲染树中消失,元素不占据空间且无法点击;visibility: hidden 不会从渲染树中消失,元素继续占据空间但无法点击;opacity: 0 不会从渲染树消失,元素占据空间且可点击。
继承性:display: none 和 opacity: 0 是非继承属性;父元素设置了 display:none 或 opacity: 0,子元素无论怎么设置都无法显示;visibility: hidden 会被子元素继承,并且子元素可以通过设置设置 visibility: visible; 来取消隐藏。
性能:display: none 会引起重排,性能消耗较大;visibility: hidden 会引起重绘,性能消耗相对较小; opacity: 0 会重建图层,性能较高

5、 link 标签和 import 标签的区别

link 属于html 标签,而 @import 是 css 提供的;
页面被加载时,link 会同时被加载,而 @import 引用的 css 会等到页面加载结束后加载;
link 方式样式的权重高于 @import 的;
link 可以使用 js 动态引入,@import不行;
link 此没有兼容性要求,而 @import 要求 IE5 以上才能识别。

6、移动端 Retina 1px 像素问题的解决方案

viewport + rem
background-image
伪元素 + transform scale()
box-shadow
7、文本显示行数控制

单行

overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
多行

overflow: hidden;
text-overflow: ellipsis;        // 超出显示'...'
display: -webkit-box;           // 将元素作为弹性伸缩盒子模型显示 。
-webkit-line-clamp: 2;          // 用来限制在一个块元素显示的文本的行数
-webkit-box-orient: vertical;   // 设置或检索伸缩盒对象的子元素的排列方式

8、清除浮动的方式
(1)
.clearfix:after {
  visibility: hidden;
  display: block;
  font-size: 0;
  content: " ";
  clear: both;
  height: 0;
}
(2)clear:both
(3)overflow:hidden

9、transition 和 animate 有何区别?

transition:用于做过渡效果,没有帧概念,只有开始和结束状态,性能开销较小
animate:用于做动画,有帧的概念,可以重复触发且有中间状态,性能开销较大

10、实现一个扇形
.sector {
  width: 0;
  height: 0;
  border-width: 50px;
  border-style: solid;
  border-color: red transparent transparent;
  border-radius: 50px;
}
三、JS篇
1、JS 的内置类型

基本类型:null、undefined、boolean、number、string、symbol
对象(Object):引用类型(也称为复杂类型)

注意: NaN 也属于 number 类型,并且 NaN 不等于自身。
2、类型判断

Typeof

console.log(typeof 1);                  // number
console.log(typeof 'a');                // string
console.log(typeof true);               // boolean
console.log(typeof undefined);          // undefined
console.log(typeof function fn(){});    // function
console.log(typeof {});                 // object
console.log(typeof null);               // object
console.log(typeof []);                 // object
console.log(typeof new Error());        // object
注意:typeof 对于基本类型,除了 null 都可以显示正确的类型;对于对象,除了函数都会显示 object

Object.prototype.toString

var number = 1;             // [object Number]
var string = '123';         // [object String]
var boolean = true;         // [object Boolean]
var und = undefined;        // [object Undefined]
var nul = null;             // [object Null]
var obj = {a: 1}            // [object Object]
var array = [1, 2, 3];      // [object Array]
var date = new Date();      // [object Date]
var error = new Error();    // [object Error]
var reg = /a/g;             // [object RegExp]
var func = function a(){};  // [object Function]

function checkType() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(Object.prototype.toString.call(arguments[i]))
    }
}

checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)

3、原型和原型链的理解

原型:每个函数都有 prototype 属性,该属性指向原型对象;使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
原型链:主要解决了继承的问题;每个对象都拥有一个原型对象,通过__proto__ 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。
图片加载失败
4、执行上下文

全局执行上下文
函数执行上下文
eval执行上下文
5、闭包

闭包是指有权访问另一个函数作用域中的变量的函数。
闭包会使得函数内部的变量都被保存在内存中,造成较大的内存开销,因此不要滥用闭包。解决的方法是在退出函数之前将不使用的局部变量置为 null ;

经典面试题:改造下面的代码,使之输出0 - 9
for (var i = 0; i< 10; i++){
    setTimeout(() => {
        console.log(i);
    }, 1000)
}

方法一、利用 setTimeout 函数的第三个参数,会作为回调函数的第一个参数传入
for (var i = 0; i < 10; i++) {
  setTimeout(i => {
    console.log(i);
  }, 1000, i)
}

方法二、使用 let 变量 的特性
for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000)
}
等价于
for (let i = 0; i < 10; i++) {
  let _i = i;// const _i = i;
  setTimeout(() => {
    console.log(_i);
  }, 1000)
}

方法三、利用函数自执行的方式,把当前 for 循环过程中的 i 传递进去,构建出块级作用域。
for (var i = 0; i < 10; i++) {
  (i => {
    setTimeout(() => {
      console.log(i);
    }, 1000)
  })(i)
}
6、this 指向的问题
this 的指向取决于函数以哪种方式调用:

作用函数调用:非严格模式下 this 指向全局对象,严格模式为 undefined
作用方法调用:this 指向调用函数的对象
构造函数调用:this 指向 new 创建出来的实例对象
call()和apply:它们的第一个参数为 this 的指向

补充:箭头函数中的 this

function a() {
    return () => {
        return () => {
            console.log(this)
        }
    }
}
console.log(a()()())
箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是 window。并且 this 一旦绑定了上下文,就不会被任何代码改变。
7、call 和 apply 的实现
Function.prototype.call2 = function(context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    
    var result = eval('context.fn(' + args + ')');
    
    delete context.fn
    return result;
}

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

8、bind 的实现
Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new TypeError("error");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    
    // 通过一个空函数作一个中转,避免绑定函数的 prototype 的属性被修改
    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}
9、new 的实现原理
new 操作符做了什么?

创建一个空对象
然后让这个空对象的__proto__指向函数的原型prototype
执行构造函数中的代码,构造函数中的this指向该对象
如果构造函数有返回值,则以该对象作为返回值。若没有return或return了基本类型,则将新对象作为返回值

function objectFactory() {

    var obj = new Object(),

    Constructor = [].shift.call(arguments);

    obj.__proto__ = Constructor.prototype;

    var ret = Constructor.apply(obj, arguments);

    return typeof ret === 'object' ? ret : obj;

};
9、instanceof 的实现
function instance_of(L, R) {
  //L 表示左表达式,R 表示右表达式
  var O = R.prototype; // 取 R 的显示原型
  L = L.__proto__; // 取 L 的隐式原型
  while (true) {
    if (L === null) return false;
    if (O === L)
      // 这里重点:当 O 严格等于 L 时,返回 true
      return true;
    L = L.__proto__;
  }
}
10、深浅拷贝

浅拷贝——如果被拷贝对象的元素是基本类型,就会拷贝出一份,并且互不影响。而如果被拷贝对象的元素是对象或者数组,就只会拷贝对象和数组的引用,此时若是在新旧对象上进行修改,都会相互影响。

// 数组浅拷贝:slice()、concat()
// 对象浅拷贝:Object.assign()、ES6的扩展运算符
复制代码
深拷贝——完全的拷贝一个对象,即使嵌套了对象,两者也互相分离,修改对象的属性,也不会影响到另一个。

// 递归实现
function clone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
            if (typeof source[i] === 'object') {
                target[i] = clone(source[i]); // 如果是引用类型,则继续遍历
            } else {
                target[i] = source[i];
            }
        }
    }

    return target;
}
当然这只是简单的实现,没有考虑到特殊的情况,如对象或数组中的函数,正则等特殊类型的拷贝等。
// JSON.parse(JSON.stringify)
var arr = [
   { value: 1 },
   { value: 2 },
   { value: 3 }
];
var copyArr = JSON.parse(JSON.stringify(arr))
copyArr[0].value = 0;
console.log(arr);       // [{value: 1}, { value: 2 }, { value: 3 }]
console.log(copyArr);   // [{value: 0}, { value: 2 }, { value: 3 }]
上面这种方法简单粗暴,缺点是不能拷贝函数。
11、防抖和节流的实现(简易版)

防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

funtion debounce(fn) {
    // 创建一个标记用来存放定时器的返回值
    let timeout = null;
    return function() {
        // 每次触发事件时都取消之前的延时调用方法
        clearTimeout(timeout);
        // 然后又创建一个新的 setTimeout, 这样就能保证 1000ms 间隔内如果重复触发就不会执行 fn 函数
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, 1000);
    };
}
节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

function throttle(fn) {
    // 通过闭包保存一个标记
    let canRun = true;
    return function(){
        // 每次开始执行函数时都先判断标记是否为 true,不为 true 则 return
        if (!canRun) return;
        // 上一次定时器执行完后 canRun 为 true,所以要先设置为false
        canRun = false;
        setTimeout(() => {
            fn.apply(this, arguments);
            // 最后在 setTimeout 执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
            canRun = true;
        }, 1000)
    }
}
12、ES5继承的实现
// 组合继承
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
}

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

SubType.prototype.sayAge = function() {
    console.log(this.age);
}
13、JS 异步解决方案的发展历程以及优缺点

回调函数(callback)

ajax('XXX1', () => {
    // callback 函数体
    ajax('XXX2', () => {
        // callback 函数体
        ajax('XXX3', () => {
            // callback 函数体
        })
    })
})
优点:解决了同步的问题
缺点:回调地狱,不能用 try catch 捕获错误,不能 return

Promise

ajax('XXX1')
  .then(res => {
      // 操作逻辑
      return ajax('XXX2')
  }).then(res => {
      // 操作逻辑
      return ajax('XXX3')
  }).then(res => {
      // 操作逻辑
  })
优点:解决了回调地狱的问题
缺点:无法取消 Promise ,错误需要通过回调函数来捕获

Generator

function *fetch() {
    yield ajax('XXX1', () => {})
    yield ajax('XXX2', () => {})
    yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()

// 配合 co 库使用
const co = require('co')

function *fetch() {
    yield ajax('XXX1', () => {})
    yield ajax('XXX2', () => {})
    yield ajax('XXX3', () => {})
}

co(fetch()).then(data => {
    //code
}).fetch(err => {
    //code
})

优点:可以控制函数的执行,配合自动执行器 co 模块 简化了手动执行的步骤
缺点:不配合 co 函数库的话使用起来比较麻烦

async/await

// async其实是一个语法糖,它的实现就是将 Generator 函数和自动执行器(co),包装在一个函数中
async function test() {
  // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
  // 如果有依赖性的话,其实就是解决回调地狱的例子了
  await fetch('XXX1')
  await fetch('XXX2')
  await fetch('XXX3')
}
read().then((data) => {
    //code
}).catch(err => {
    //code
});

优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
14、setTimeout、Promise、Async/Await 的区别

setTimeout —— setTimeout的回调函数会放到宏任务队列里,等到执行栈清空以后执行

console.log('script start')    //1. 打印 script start
setTimeout(function() {
    console.log('settimeout')    // 4. 打印 settimeout
})    // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end')    //3. 打印 script start
// 输出顺序:script start->script end->settimeout

Promise —— Promise本身是同步的立即执行函数, 当在 executor 中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行。

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function() {
    console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout

async/await —— async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

async function async1() {
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}

console.log('script start');
async1();
console.log('script end')

// 输出顺序:script start->async1 start->async2->script end->async1 end
15、Promise的简单实现
promise 的使用(有关 Promise 的详细用法,可参考 阮一峰老师的ES6文档)
var promise = new Promise((resolve,reject) => {
    if (操作成功) {
        resolve(value)
    } else {
        reject(error)
    }
})
promise.then(function (value) {
    // success
},function (value) {
    // failure
})
简单实现
function myPromise(constructor) {
    let self = this;
    self.status = "pending"   // 定义状态改变前的初始状态
    self.value = undefined;   // 定义状态为resolved的时候的状态
    self.reason = undefined;  // 定义状态为rejected的时候的状态
    function resolve(value) {
       if(self.status === "pending") {
          self.value = value;
          self.status = "resolved";
       }
    }
    function reject(reason) {
       if(self.status === "pending") {
          self.reason = reason;
          self.status = "rejected";
       }
    }
    // 捕获构造异常
    try {
       constructor(resolve,reject);
    } catch(e) {
       reject(e);
    }
}
添加 then 方法
myPromise.prototype.then = function(onFullfilled,onRejected) {
   let self = this;
   switch(self.status) {
      case "resolved":
        onFullfilled(self.value);
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
   }
}

var p = new myPromise(function(resolve,reject) {
    resolve(1)
});
p.then(function(x) {
    console.log(x) // 1
})

16、前端模块化发展历程

IIFE: 使用自执行函数来编写模块化,特点:在一个单独的函数作用域中执行代码,避免变量冲突。

(function(){
  return {
    data:[]
  }
})()

AMD: 使用 requireJS 来编写模块化,特点:依赖必须提前声明好。

define('./index.js',function(code){
    // code 就是index.js 返回的内容
})

CMD: 使用 seaJS 来编写模块化,特点:对于依赖的模块是延迟执行,依赖可以就近书写,等到需要用这个依赖的时候再引入这个依赖,支持动态引入依赖文件。

define(function(require, exports, module) {  
  var indexCode = require('./index.js');
});

CommonJS: nodejs 中自带的模块化。

var fs = require('fs');

ES Modules: ES6 引入的模块化,支持 import 来引入另一个 js 。

import a from 'a';
17、ES6 模块和 CommonJS 模块的差异?

ES6模块在编译时,就能确定模块的依赖关系,以及输入和输出的变量;CommonJS 模块,运行时加载。
ES6 模块自动采用严格模式,无论模块头部是否写了 "use strict";
require 可以做动态加载,import 语句做不到,import 语句必须位于顶层作用域中。
ES6 模块中顶层的 this 指向 undefined,CommonJS 模块的顶层 this 指向当前模块。
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值