106 道面向初中级前端的基础面试题

11,062 阅读43分钟

JavaScript

1. 什么是同源策略?

同源策略可防止 JavaScript 发起跨域请求。源被定义为协议、主机名和端口号的组合。此策略可防止页面上的恶意脚本通过该页面的文档对象模型,访问另一个网页上的敏感数据。

下表给出了与 URL store.company.com/dir/page.ht… 的源进行对比的示例:

URL结果原因
store.company.com/dir2/other.…同源只有路径不同
store.company.com/dir/inner/a…同源只有路径不同
store.company.com/secure.html失败协议不同
store.company.com:81/dir/etc.htm…失败端口不同 ( http:// 默认端口是80)
news.company.com/dir/other.h…失败主机不同

参考资料:

2. 跨域是什么?

  • 原因
    浏览器的同源策略导致了跨域
  • 作用
    用于隔离潜在恶意文件的重要安全机制
  • 解决
  1. jsonp ,允许 script 加载第三方资源
  2. 反向代理(nginx 服务内部配置 Access-Control-Allow-Origin *)
  3. cors 前后端协作设置请求头部,Access-Control-Allow-Origin 等头部信息
  4. iframe 嵌套通讯,postmessage

参考资料:

3. JSONP 是什么?

这是我认为写得比较通俗易懂的一篇文章jsonp原理详解——终于搞清楚jsonp是啥了

4. 事件绑定的方式

  • 嵌入dom
<button onclick="func()">按钮</button>
  • 直接绑定
btn.onclick = function(){}
  • 事件监听
btn.addEventListener('click',function(){})

5. 事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存。

<ul>
  <li>苹果</li>
  <li>香蕉</li>
  <li>凤梨</li>
</ul>

// good
document.querySelector('ul').onclick = (event) => {
  let target = event.target
  if (target.nodeName === 'LI') {
    console.log(target.innerHTML)
  }
}

// bad
document.querySelectorAll('li').forEach((e) => {
  e.onclick = function() {
    console.log(this.innerHTML)
  }
}) 

6. 事件循环

事件循环是一个单线程循环,用于监视调用堆栈并检查是否有工作即将在任务队列中完成。如果调用堆栈为空并且任务队列中有回调函数,则将回调函数出队并推送到调用堆栈中执行。

7. 如何自定义事件

新模式

const div = document.createElement('div') // 不创建元素,直接用 window 对象也可以
const event = new Event('build')

div.addEventListener('build', function(e) {
    console.log(111)
})

div.dispatchEvent(event)

过时的模式

  1. 原生提供了3个方法实现自定义事件
  2. document.createEvent('Event') 创建事件
  3. initEvent 初始化事件
  4. dispatchEvent 触发事件
const events = {}

function registerEvent(name) {
    const event = document.createEvent('Event')
    event.initEvent(name, true, true) // 事件名称,是否允许冒泡,该事件的默认动作是否可以被取消
    events[name] = event
}

function triggerEvent(name) {
    window.dispatchEvent(events[name])
}

registerEvent('resize') // 注册 resize 事件
triggerEvent('resize') // 触发 resize 事件

MDN

8. target 和 currentTarget 区别

  • event.target 返回触发事件的元素
  • event.currentTarget 返回绑定事件的元素

9. prototype 和 proto 的关系是什么

  1. prototype 用于访问函数的原型对象。
  2. __proto__ 用于访问对象实例的原型对象(或者使用 Object.getPrototypeOf())。
function Test() {}
const test = new Test()
test.__proto__ == Test.prototype // true

也就是说,函数拥有 prototype 属性,对象实例拥有 __proto__ 属性,它们都是用来访问原型对象的。

函数有点特别,它不仅是个函数,还是个对象。所以它也有 __proto__ 属性。

为什么会这样呢?因为函数是内置构造函数 Function 的实例:

const test = new Function('function Test(){}')
test.__proto__ == Function.prototype // true

所以函数能通过 __proto__ 访问它的原型对象。

由于 prototype 是一个对象,所以它也可以通过 __proto__ 访问它的原型对象。对象的原型对象,那自然是 Object.prototype 了。

function Test() {}
Test.prototype.__proto__ == Object.prototype // true

这样看起来好像有点复杂,我们可以换个角度来看。Object 其实也是内置构造函数:

const obj = new Object()
obj.__proto__ == Object.prototype // true

从这一点来看,是不是更好理解一点。

为了防止无休止的循环下去,所以 Object.prototype.__proto__ 是指向 null 的,null 是万物的终点。

Object.prototype.__proto__ == null // true

既然 null 是万物的终点,那使用 Object.create(null) 创建的对象是没有 __proto__ 属性的,也没有 prototype 属性。

10. 原型继承

所有的 JS 对象(JS 函数是 prototype)都有一个 __proto__ 属性,指向它的原型对象。当试图访问一个对象的属性时,如果没有在该对象上找到,它还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

11. 继承

寄生组合式继承

function SuperType(name) {
    this.name = name
    this.colors = ['red']
}

SuperType.prototype.sayName = function() {
    console.log(this.name)
}
// 继承实例属性
function SubType(name, age) {
    SuperType.call(this, name)
    this.age = age
}

function inheritPrototype(subType, superType) {
    let prototype = Object.create(superType.prototype)
    prototype.constructor = subType
    subType.prototype = prototype
}
// 继承原型方法
inheritPrototype(SubType, SuperType)

// 定义自己的原型方法
SubType.prototype.sayAge = function() {
    console.log(this.age)
}

12. 闭包

闭包是指有权访问另一个函数作用域中的变量的函数。

function sayHi(name) {
    return () => {
       console.log(`Hi! ${name}`)
    }
}
const test = sayHi('xiaoming')
test() // Hi! xiaoming

虽然sayHi函数已经执行完毕,但是其活动对象也不会被销毁,因为test函数仍然引用着sayHi函数中的变量name,这就是闭包。

但也因为闭包引用着另一个函数的变量,导致另一个函数即使不使用了也无法销毁,所以闭包使用过多,会占用较多的内存,这也是一个副作用。

利用闭包实现私有属性

const test = (function () {
    let value = 0
    return {
        getVal() { return value },
        setVal(val) { value = val }
    } 
})()

上面的代码实现了一个私有属性 value,它只能用过 getVal() 来取值,通过 setVal(val) 来设置值。

13. 内存回收

在 JS 中,有两种内存回收算法。第一种是引用计数垃圾收集,第二种是标记-清除算法(从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法)。

引用计数垃圾收集

如果一个对象没有被其他对象引用,那它将被垃圾回收机制回收。

let o = { a: 1 }

一个对象被创建,并被 o 引用。

o = null

刚才被 o 引用的对象现在是零引用,将会被回收。

循环引用

引用计数垃圾收集有一个缺点,就是循环引用会造成对象无法被回收。

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

在 f() 执行后,函数的局部变量已经没用了,一般来说,这些局部变量都会被回收。但上述例子中,o 和 o2 形成了循环引用,导致无法被回收。

标记-清除算法

这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

对于刚才的例子来说,在 f() 执行后,由于 o 和 o2 从全局对象出发无法获取到,所以它们将会被回收。

高效使用内存

在 JS 中能形成作用域的有函数、全局作用域、with,在 es6 还有块作用域。局部变量随着函数作用域销毁而被释放,全局作用域需要进程退出才能释放或者使用 delete 和赋空值 null undefined

在 V8 中用 delete 删除对象可能会干扰 V8 的优化,所以最好通过赋值方式解除引用。

参考资料:

14. 有一个函数,参数是一个函数,返回值也是一个函数,返回的函数功能和入参的函数相似,但这个函数只能执行3次,再次执行无效,如何实现

这个题目是考察闭包的使用

function sayHi() {
    console.log('hi')
}

function threeTimes(fn) {
    let times = 0
    return () => {
        if (times++ < 3) {
            fn()
        }
    }
}

const newFn = threeTimes(sayHi)
newFn()
newFn()
newFn()
newFn()
newFn() // 后面两次执行都无任何反应

通过闭包变量 times 来控制函数的执行

15. 实现add函数,让add(a)(b)和add(a,b)两种调用结果相同

实现1

function add(a, b) {
    if (b === undefined) {
        return function(x) {
            return a + x
        }
    }

    return a + b
}

实现2——柯里化

function curry(fn, ...args1) {
    // length 是函数对象的一个属性值,指该函数有多少个必须要传入的参数,即形参的个数。
    if (fn.length == args1.length) {
        return fn(...args1)
    }

    return function(...args2) {
        return curry(fn, ...args1, ...args2)
    }
}

function add(a, b) {
    return a + b
}

console.log(curry(add, 1)(2)) // 3
console.log(curry(add, 1, 2)) // 3

16. 使用Ajax的优缺点分别是什么

优点

  • 交互性更好。来自服务器的新内容可以动态更改,无需重新加载整个页面。
  • 减少与服务器的连接,因为脚本和样式只需要被请求一次。
  • 状态可以维护在一个页面上。JavaScript 变量和 DOM 状态将得到保持,因为主容器页面未被重新加载。
  • 基本上包括大部分 SPA 的优点。

缺点

  • 动态网页很难收藏。
  • 如果 JavaScript 已在浏览器中被禁用,则不起作用。
  • 有些网络爬虫不执行 JavaScript,也不会看到 JavaScript 加载的内容。
  • 基本上包括大部分 SPA 的缺点。

参考资料:

17. Ajax和Fetch区别

  • ajax是使用XMLHttpRequest对象发起的,但是用起来很麻烦,所以ES6新规范就有了fetch,fetch发一个请求不用像ajax那样写一大堆代码。
  • 使用fetch无法取消一个请求,这是因为fetch基于Promise,而Promise无法做到这一点。
  • 在默认情况下,fetch不会接受或者发送cookies
  • fetch没有办法原生监测请求的进度,而XMLHttpRequest可以
  • fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
  • fetch由于是ES6规范,兼容性上比不上XMLHttpRequest

18. 变量提升

var会使变量提升,这意味着变量可以在声明之前使用。let和const不会使变量提升,提前使用会报错。

变量提升(hoisting)是用于解释代码中变量声明行为的术语。使用var关键字声明或初始化的变量,会将声明语句“提升”到当前作用域的顶部。 但是,只有声明才会触发提升,赋值语句(如果有的话)将保持原样。

19. 使用let、var和const创建变量有什么区别

用 var 声明的变量的作用域是它当前的执行上下文,它可以是嵌套的函数,也可以是声明在任何函数外的变量。let 和 const 是块级作用域,意味着它们只能在最近的一组花括号(function、if-else 代码块或 for 循环中)中访问。

var 声明的全局变量和函数都会成为 window 对象的属性和方法。使用 let 和 const 的顶级声明不会定义在全局上下文中,但在作用域链解析上效果是一样的。

function foo() {
  // 所有变量在函数中都可访问
  var bar = 'bar';
  let baz = 'baz';
  const qux = 'qux';

  console.log(bar); // bar
  console.log(baz); // baz
  console.log(qux); // qux
}

console.log(bar); // ReferenceError: bar is not defined
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined
if (true) {
  var bar = 'bar';
  let baz = 'baz';
  const qux = 'qux';
}

// 用 var 声明的变量在函数作用域上都可访问
console.log(bar); // bar
// let 和 const 定义的变量在它们被定义的语句块之外不可访问
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined

var会使变量提升,这意味着变量可以在声明之前使用。let和const不会使变量提升,提前使用会报错。

console.log(foo); // undefined

var foo = 'foo';

console.log(baz); // ReferenceError: can't access lexical declaration 'baz' before initialization

let baz = 'baz';

console.log(bar); // ReferenceError: can't access lexical declaration 'bar' before initialization

const bar = 'bar';

用var重复声明不会报错,但let和const会。

var foo = 'foo';
var foo = 'bar';
console.log(foo); // "bar"

let baz = 'baz';
let baz = 'qux'; // Uncaught SyntaxError: Identifier 'baz' has already been declared

let和const的区别在于:let允许多次赋值,而const只允许一次。

// 这样不会报错。
let foo = 'foo';
foo = 'bar';

// 这样会报错。
const baz = 'baz';
baz = 'qux';

20. 对象浅拷贝和深拷贝有什么区别

JS 中,除了基本数据类型,还存在对象、数组这种引用类型。 基本数据类型,拷贝是直接拷贝变量的值,而引用类型拷贝的其实是变量的地址。

let o1 = {a: 1}
let o2 = o1

在这种情况下,如果改变 o1o2 其中一个值的话,另一个也会变,因为它们都指向同一个地址。

o2.a = 3
console.log(o1.a) // 3

而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有重新创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。

21. 怎么实现对象深拷贝

这种方法有缺陷,详情请看关于JSON.parse(JSON.stringify(obj))实现深拷贝应该注意的坑

let o1 = {a:{
    b:1
  }
}
let o2 = JSON.parse(JSON.stringify(o1))

基础版

function deepCopy(target) {
    if (typeof target == 'object') {
        const result = Array.isArray(target)? [] : {}
        for (const key in target) {
            if (typeof target[key] == 'object') {
                result[key] = deepCopy(target[key])
            } else {
                result[key] = target[key]
            }
        }

        return result
    } else if (typeof target == 'function') {
        return eval('(' + test.toString() + ')')
    } else {
        return target
    }
}

完整版

const mapTag = '[object Map]'
const setTag = '[object Set]'
const arrayTag = '[object Array]'
const objectTag = '[object Object]'
const symbolTag = '[object Symbol]'

function deepCopy(origin, map = new WeakMap()) {
    if (!origin || !isObject(origin)) return origin
    if (typeof origin == 'function') {
        return eval('(' + origin.toString() + ')')
    }

    const objType = getObjType(origin)
    const result = createObj(origin, objType)

    // 防止循环引用,不会遍历已经在 map 中的对象,因为在上一层正在遍历
    if (map.get(origin)) {
        return map.get(origin)
    }
    
    map.set(origin, result)
    
    // set
    if (objType == setTag) {
        for (const value of origin) {
            result.add(deepCopy(value, map))
        }

        return result
    }

    // map
    if (objType == mapTag) {
        for (const [key, value] of origin) {
            result.set(key, deepCopy(value, map))
        }

        return result
    }

    // 对象或数组
    if (objType == objectTag || objType == arrayTag) {
        for (const key in origin) {
            result[key] = deepCopy(origin[key], map)
        }

        return result
    }

    return result
}

function getObjType(obj) {
    return Object.prototype.toString.call(obj)
}

function createObj(obj, type) {
    if (type == objectTag) return {}
    if (type == arrayTag) return []
    if (type == symbolTag) return Object(Symbol.prototype.valueOf.call(obj))
    
    return new obj.constructor(obj)
}

function isObject(origin) {
    return typeof origin == 'object' || typeof origin == 'function'
}

如何写出一个惊艳面试官的深拷贝?

22. 数组去重

ES5

function unique(arry) {
    const temp = []
    arry.forEach(function(item) {
        if (temp.indexOf(item) == -1) {
            temp.push(item)
        }
    })
    
    return temp
}

ES6

function unique(arry) {
   return Array.from(new Set(arry))
}

23. 数据类型

  1. Undefined
  2. Null
  3. Boolean
  4. Number
  5. String
  6. Object
  7. symbol(ES6新增)

24. 内置函数(原生函数)

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error
  • Symbol

原始值 "I am a string" 并不是一个对象,它只是一个字面量,并且是一个不可变的值。

如果要在这个字面量上执行一些操作,比如获取长度、访问其中某个字符等,那需要将其转换为 String 对象。

幸好,在必要时语言会自动把字符串字面量转换成一个 String 对象,也就是说你并不需要显式创建一个对象。

25. 如何判断数组与对象

Array.isArray([]) // true
Array.isArray({}) // false

typeof [] // "object"
typeof {} // "object"

Object.prototype == [].__proto__ // false
Object.prototype == {}.__proto__ // true
Array.prototype == [].__proto__ // true
Array.prototype == {}.__proto__ // false

26. 自动分号

有时 JavaScript 会自动为代码行补上缺失的分号,即自动分号插入(Automatic SemicolonInsertion,ASI)。

因为如果缺失了必要的 ; ,代码将无法运行,语言的容错性也会降低。ASI 能让我们忽略那些不必要的 ;

请注意,ASI 只在换行符处起作用,而不会在代码行的中间插入分号。

如果 JavaScript 解析器发现代码行可能因为缺失分号而导致错误,那么它就会自动补上分号。并且,只有在代码行末尾与换行符之间除了空格和注释之外没有别的内容时,它才会这样做。

27. 浮点数精度

www.css88.com/archives/73…

28. cookie、localStorage、sessionStorage区别

特性cookielocalStoragesessionStorage
由谁初始化客户端或服务器,服务器可以使用Set-Cookie请求头。客户端客户端
数据的生命周期一般由服务器生成,可设置失效时间,如果在浏览器生成,默认是关闭浏览器之后失效永久保存,可清除仅在当前会话有效,关闭页面后清除
存放数据大小4KB5MB5MB
与服务器通信每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题仅在客户端保存仅在客户端保存
用途一般由服务器生成,用于标识用户身份用于浏览器缓存数据用于浏览器缓存数据
访问权限任意窗口任意窗口当前页面窗口

29. 自执行函数?用于什么场景?好处?

自执行函数:
  1. 声明一个匿名函数
  2. 马上调用这个匿名函数。

作用:创建一个独立的作用域。

好处
  • 防止变量弥散到全局,以免各种js库冲突。
  • 隔离作用域避免污染,或者截断作用域链,避免闭包造成引用变量无法释放。
  • 利用立即执行特性,返回需要的业务函数或对象,避免每次通过条件判断来处理。
场景

一般用于框架、插件等场景

30. 多个页面之间如何进行通信

有如下几个方式:

  • cookie
  • web worker
  • localeStorage和sessionStorage

31. css动画和js动画的差异

  1. 代码复杂度,js 动画代码相对复杂一些
  2. 动画运行时,对动画的控制程度上,js 能够让动画,暂停,取消,终止,css动画不能添加事件
  3. 动画性能看,js 动画多了一个js 解析的过程,性能不如 css 动画好

zhuanlan.zhihu.com/p/41479807

32. new一个对象经历了什么

function Test(){}
const test = new Test()
  1. 创建一个新对象:
const obj = {}
  1. 设置新对象的constructor属性为构造函数的名称,设置新对象的__proto__属性指向构造函数的prototype对象
obj.constructor = Test
obj.__proto__ = Test.prototype
  1. 使用新对象调用函数,函数中的this被指向新实例对象
Test.call(obj)
  1. 将初始化完毕的新对象地址,保存到等号左边的变量中

33. bind、call、apply的区别

call和apply其实是一样的,区别就在于传参时参数是一个一个传或者是以一个数组的方式来传。
call和apply都是在调用时生效,改变调用者的this指向。

let name = 'Jack'
const obj = {name: 'Tom'}
function sayHi() {console.log('Hi! ' + this.name)}

sayHi() // Hi! Jack
sayHi.call(obj) // Hi! Tom

bind也是改变this指向,不过不是在调用时生效,而是返回一个新函数。

const newFunc = sayHi.bind(obj)
newFunc() // Hi! Tom

34. 实现 bind call apply 函数

bind

Function.prototype.bind = function(context, ...extra) {
    const self = this
    // 这里不能用箭头函数,防止绑定函数为构造函数
    return function(...arg) {
        return self.call(context, ...extra.concat(arg))
    }
}

call

Function.prototype.call = function(context, ...args) {
    if (context === null || context === undefined) {
        context = window
    } else if (!context || context.toString() != '[object Object]') {
        context = {}
    }

    let key = Math.random()
    while (context[key]) {
        key = Math.random()
    }

    context[key] = this
    const result = context[key](...args)
    delete context[key]
    return result
}

apply

Function.prototype.apply = function(context, args) {
    if (args !== undefined && !Array.isArray(args)) throw '参数必须为数组'
    if (context === null || context === undefined) {
        context = window
    } else if (!context || context.toString() != '[object Object]') {
        context = {}
    }

    let key = Math.random()
    while (context[key]) {
        key = Math.random()
    }

    context[key] = this
    let result
    if (args === undefined) {
        const result = context[key]()
    } else {
        const result = context[key](...args)
    }

    delete context[key]
    return result
}

35. 请简述JavaScript中的this

JS 中的this是一个相对复杂的概念,不是简单几句能解释清楚的。粗略地讲,函数的调用方式决定了this的值。我阅读了网上很多关于this的文章,Arnav Aggrawal 写的比较清楚。this取值符合以下规则:

  1. 在调用函数时使用new关键字,函数内的this是一个全新的对象。
  2. 如果applycallbind方法用于调用、创建一个函数,函数内的 this 就是作为参数传入这些方法的对象。
  3. 当函数作为对象里的方法被调用时,函数内的this是调用该函数的对象。比如当obj.method()被调用时,函数内的 this 将绑定到obj对象。
  4. 如果调用函数不符合上述规则,那么this的值指向全局对象(global object)。浏览器环境下this的值指向window对象,但是在严格模式下('use strict'),this的值为undefined
  5. 如果符合上述多个规则,则较高的规则(1 号最高,4 号最低)将决定this的值。
  6. 如果该函数是 ES2015 中的箭头函数,将忽略上面的所有规则,this被设置为它被创建时的上下文。

想获得更深入的解释,请查看这篇文章

36. 如何确定this指向

如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。

  1. 由 new 调用?绑定到新创建的对象。
  2. 由 call 或者 apply (或者 bind )调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到 undefined ,否则绑定到全局对象。

一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑定,你可以使用一个 DMZ 对象,比如 ø = Object.create(null) ,以保护全局对象。
ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this ,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这其实和 ES6 之前代码中的 self = this 机制一样

参考资料:

37. ==和===的区别是什么

==是抽象相等运算符,而===是严格相等运算符。==运算符是在进行必要的类型转换后,再比较。===运算符不会进行类型转换,所以如果两个值不是相同的类型,会直接返回false。使用==时,可能发生一些特别的事情,例如:

1 == '1'; // true
1 == [1]; // true
1 == true; // true
0 == ''; // true
0 == '0'; // true
0 == false; // true

如果你对=====的概念不是特别了解,建议大多数情况下使用===

38. 箭头函数和普通函数有什么区别

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象,用call apply bind也不能改变this指向
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  • 箭头函数没有原型对象prototype

39. 白屏时间

白屏时间是指浏览器从输入网址,到浏览器开始显示内容的时间。

Performance 接口可以获取到当前页面中与性能相关的信息,该类型的对象可以通过调用只读属性 Window.performance 来获得。

performance.timing.navigationStart 是一个返回代表一个时刻的 unsigned long long 型只读属性,为紧接着在相同的浏览环境下卸载前一个文档结束之时的 Unix 毫秒时间戳。如果没有上一个文档,则它的值相当于 PerformanceTiming.fetchStart。

所以将以下脚本放在 </head> 前面就能获取白屏时间。

<script>
	new Date() - performance.timing.navigationStart
</script>

参考资料:

40. 当你在浏览器输入一个地址后发生了什么

当···时发生了什么?

41. 页面大量图片,如何优化加载,优化用户体验

  1. 图片懒加载。在页面的未可视区域添加一个滚动事件,判断图片位置与浏览器顶端的距离与页面的距离,如果前者小于后者,优先加载。
  2. 如果为幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。
  3. 如果图片为css图片,可以使用CSSsprite,SVGsprite等技术。
  4. 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩的特别厉害的缩略图,以提高用户体验。
  5. 如果图片展示区域小于图片的真实大小,应在服务器端根据业务需要先进行图片压缩,图片压缩后大小与展示一致。

42. js网络请求性能优化之防抖与节流

防抖(debounce)

在函数需要频繁触发时,只有当有足够空闲的时间时,才执行一次。就好像在百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发。

节流(thorttle)

预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。就好像你在淘宝抢购某一件限量热卖商品时,你不断点刷新点购买,可是总有一段时间你点上是没有效果,这里就用到了节流,就是怕点的太快导致系统出现bug。

区别

在发生持续触发事件时,防抖设置事件延迟并在空闲时间去触发事件,而节流则是隔一定的时间触发一次。

一个简单的防抖示例

let timer
input.on('input', () => {
    clearTimeout(timer)
    // 停止输入 500 毫秒后开始搜索
    timer = setTimeout(() => {
	// 搜索
    }, 500)
})

一个简单的节流示例

let isClick = false
button.on('click', () => {
    if (isClick) return
    isClick = true
    // 其他代码。。。
    // 每 10 秒只允许点击一次
    setTimeout(() => {
	isClick = false
    }, 10000)
})

参考资料:

43. 如何做到修改url参数页面不刷新

HTML5引入了 history.pushState()history.replaceState() 方法,它们分别可以添加和修改历史记录条目。

let stateObj = {
    foo: "bar",
};

history.pushState(stateObj, "page 2", "bar.html");

假设当前页面为 foo.html,执行上述代码后会变为 bar.html,点击浏览器后退,会变为 foo.html,但浏览器并不会刷新。 pushState() 需要三个参数: 一个状态对象, 一个标题 (目前被忽略), 和 (可选的) 一个 URL. 让我们来解释下这三个参数详细内容:

  • 状态对象 — 状态对象 state 是一个 JavaScript 对象,通过 pushState () 创建新的历史记录条目。无论什么时候用户导航到新的状态,popstate 事件就会被触发,且该事件的 state 属性包含该历史记录条目状态对象的副本。 状态对象可以是能被序列化的任何东西。原因在于 Firefox 将状态对象保存在用户的磁盘上,以便在用户重启浏览器时使用,我们规定了状态对象在序列化表示后有640k的大小限制。如果你给 pushState() 方法传了一个序列化后大于 640k 的状态对象,该方法会抛出异常。如果你需要更大的空间,建议使用 sessionStorage 以及 localStorage.

  • 标题 — Firefox 目前忽略这个参数,但未来可能会用到。传递一个空字符串在这里是安全的,而在将来这是不安全的。二选一的话,你可以为跳转的 state 传递一个短标题。

  • URL — 该参数定义了新的历史URL记录。注意,调用 pushState() 后浏览器并不会立即加载这个 URL,但可能会在稍后某些情况下加载这个 URL,比如在用户重新打开浏览器时。新URL不必须为绝对路径。如果新URL是相对路径,那么它将被作为相对于当前 URL 处理。新 URL 必须与当前URL同源,否则 pushState() 会抛出一个异常。该参数是可选的,缺省为当前 URL。

参考资料:

44. 请用js去除字符串空格

去除所有空格

str.replace(/\s/g, '')

去除两边空格

str.replace(/^\s+|\s+$/g, '')
// 原生方法
str.trim()

45. 创建对象有几种方法

  • 字面量
const obj = {a: 1}
  • 构造函数
function Obj(val) {
    this.a = val
}

const obj = new Obj(1)
  • Object.create
const obj = Object.create({a: 1})

46. null和undefined的区别

null 表示一个对象是“没有值”的值,也就是值为“空”

undefined 表示一个变量声明了没有初始化(赋值)

undefinednull 在if语句中,都会被自动转为false

undefined 不是一个有效的JSON,而 null

undefined 的类型(typeof)是 undefined

null 的类型(typeof)是 object

Javascript将未赋值的变量默认值设为 undefined

Javascript从来不会将变量设为 null。 它是用来让程序员表明某个用var声明的变量时没有值的

47. 异步求和

要求

假设有一台本地机器,无法做加减乘除运算,因此无法执行 a + b、a+ = 1 这样的 JS 代码,然后我们提供一个服务器端的 HTTP API,可以传两个数字类型的参数,响应结果是这两个参数的和,这个 HTTP API 的 JS SDK(在本地机器上运行)的使用方法如下:

asyncAdd(3, 5, (err, result) => {
  console.log(result); // 8
});

模拟实现:

function asyncAdd(a, b, cb) {
  setTimeout(() => {
    cb(null, a + b);
  }, Math.floor(Math.random()*100))
}

现在要求在本地机器上实现一个 sum 函数,支持以下用法:

(async () => {
  const result1 = await sum(1, 4, 6, 9, 1, 4);
  const result2 = await sum(3, 4, 9, 2, 5, 3, 2, 1, 7);
  const result3 = await sum(1, 6, 0, 5);
  console.log([result1, result2, result3]); // [25, 36, 12]
})();

要求 sum 能在最短的时间里返回以上结果

实现

function asyncAdd(a, b, cb) {
    setTimeout(() => {
        cb(null, a + b);
    }, Math.floor(Math.random()*100))
}

function sum(...args) {
    const result = []
    function _sum(resolve, reject) {
        new Promise((r, j) => {
            let a = args.pop()
            let b = args.pop()
            a = a !== undefined? a : 0
            b = b !== undefined? b : 0 // 如果访问的元素超出了数组范围,则转为 0
            asyncAdd(a, b, (err, res) => {
                if (err) j(err)
                r(res)
            })

            if (args.length) {
                _sum(resolve, reject)
            }
        })
        .then(val => {
            result.push(val)
            setTimeout(() => {
                if (args.length <= 0) {
                    resolve(sum(...result))
                }
            }, 100)
        })
    }

    return new Promise((resolve, reject) => {
        if (!args || !args.length) resolve(0)
        if (args.length == 1) resolve(args[0])
        _sum(resolve, reject)
    })
}

(async () => {
    const result1 = await sum(1, 4, 6, 9, 1, 4)
    const result2 = await sum(3, 4, 9, 2, 5, 3, 2, 1, 7)
    const result3 = await sum(1, 6, 0, 5)
    console.log([result1, result2, result3]) // [25, 36, 12]
})()

48. CommonJS,ES module 是什么,有什么区别?

它们都是一种模块规范,例如 Node 使用的就是 CommonJS 规范。ES module 则是语言标准上的模块规范。

区别:

  1. CommonJS 模块使用 require()module.exports,ES6 模块使用 importexport
  2. CommonJS 模块输出的是一个值的浅拷贝,ES6 模块输出的是值的引用。
  3. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  4. CommonJS 模块的 require() 是同步加载模块,ES6 模块的 import 命令是异步加载,有一个独立的模块依赖的解析阶段。
  5. ES6 模块之中,顶层的 this 指向 undefined;CommonJS 模块的顶层 this 指向当前模块,
  6. 对于循环加载的处理方法不同

第 3 个差异是因为 CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。

而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

参考资料:

49. preload和prefetch

preload

preload<link> 标签 rel 属性的属性值,同时需要配合 as 属性使用。

as 指定将要预加载的内容的类型,使得浏览器能够:

  1. 更精确地优化资源加载优先级。
  2. 匹配未来的加载需求,在适当的情况下,重复利用同一资源。
  3. 为资源应用正确的内容安全策略。
  4. 为资源设置正确的 Accept 请求头。

看一下这个示例:

<link rel="preload" href="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" as="script">

这种做法将把 <link> 标签塞入一个预加载器中。

这个预加载器在不阻塞页面 onload 事件的情况下,去加载资源。

我们可以通过以下两个示例来作一个对比:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        console.time('load')
    </script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/echarts/2.1.10/chart/bar.js"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<script>
window.onload = () => {
    console.timeEnd('load') // load: 1449.759033203125ms
}
</script>
</body>
</html>

上面这个示例从加载到触发 onload 事件需要大概 1400 ms 的时间。再看一下使用 preload 预加载的时间:

<link rel="preload" href="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" as="script">
<link rel="preload" href="https://cdn.bootcdn.net/ajax/libs/echarts/2.1.10/chart/bar.js" as="script">
<link rel="preload" href="https://unpkg.com/element-ui/lib/index.js" as="script">
<link rel="preload" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" as="style">

window.onload = () => {
    console.timeEnd('load') // load: 10.8818359375ms
}

用 preload 来加载资源,只需要 10 ms 就触发了 onload 事件。

说明同样是下载文件,使用 preload 不会阻塞 onload 事件。

prefetch

prefetchpreload 不同,使用 prefetch 属性指定的资源将在浏览器空闲时间下下载。

在资源的请求头如果发现有下面这个属性,就代表它是通过 prefetch 加载的:

purpose: prefetch

另外,空闲时间是如何确定、如何获取的,目前还没有相关 API。

50. preload 和 defer 的区别

preload 和 defer 的相同点是异步下载。那它们的不同点是什么呢?

preload 下载的资源只有在遇到同样的 script 标签时,才会执行对应的脚本。例如下面预加载的 vue.js

<link rel="preload" as="script" href="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">

只有在遇到下面的标签时,才会执行加载的 vue.js

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

defer 则是异步下载资源,在所有元素解析完成后,触发 DOMContentLoaded 事件前执行。

51. window.onload 和 DOMContentLoaded 的区别

当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load事件。

它与 DOMContentLoaded不同,当纯HTML被完全加载以及解析时,DOMContentLoaded 事件会被触发,而不必等待样式表,图片或者子框架完成加载。

52. Object 与 Map 的区别

  1. Object 只能选择字符、数值、符号作为 key,Map 则可以使用任何类型的数据作为 key。
  2. Map 实例会维护键值对的插入顺序,因此可以根据插入顺序执行迭代操作。Chrome Opera 中使用 for-in 语句遍历 Object 属性时会遵循一个规律:它们会先提取所有 key 的 parseFloat 值为非负整数的属性,然后根据数字顺序对属性排序首先遍历出来,然后按照对象定义的顺序遍历余下的所有属性。其它浏览器则完全按照对象定义的顺序遍历属性。

选择 Object 还是 Map

对于多数Web开发任务来说,选择 Object 还是 Map 只是个人偏好问题,影响不大。不过,对于在乎内存和性能的开发者来说,对象和映射之间确实存在显著的差别。

1. 内存占用

Object 和 Map 的工程级实现在不同浏览器间存在明显差异,但存储单个键/值对所占用的内存数量都会随键的数量线性增加。批量添加或删除键/值对则取决于各浏览器对该类型内存分配的工程实现。

不同浏览器的情况不同,但给定固定大小的内存, Map 大约可以比 Object 多存储50%的键/值对。

2. 插入性能

向 Object 和 Map 中插入新键/值对的消耗大致相当,不过插入Map 在所有浏览器中一般会稍微快一点儿。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。

如果代码涉及大量插入操作,那么显然 Map 的性能更佳。

3. 查找速度

与插入不同,从大型 Object 和 Map 中查找键/值对的性能差异极小,但如果只包含少量键/值对,则 Object 有时候速度更快。在把 Object 当成数组使用的情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效的布局。

这对 Map 来说是不可能的。对这两个类型而言,查找速度不会随着键/值对数量增加而线性增加。如果代码涉及大量查找操作,那么某些情况下可能选择 Object 更好一些。

4. 删除性能

使用 delete 删除 Object 属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。为此,出现了一些伪删除对象属性的操作,包括把属性值设置为 undefined 或 null 。但很多时候,这都是一 种讨厌的或不适宜的折中。

而对大多数浏览器引擎来说, Map 的 delete() 操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择 Map 。

参考资料:

53. 为什么 WeakMap 和 WeakSet 的键只能使用对象?

是为了保证只有通过键对象的引用来取得值。

const m = new WeakMap()
m.set({}, 100) // 由于 {} 没有在其他地方引用,所以在垃圾回收时,这个值也会被回收。

const a = {}
m.set(a, 100) // 如果使用这种方式,则不会被回收。因为 {} 有 a 变量在引用它。

a = null // 将 a 置为空后,m 里的值 100 在垃圾回收时将会被回收。

如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。

所以这句话的意思很明确:

const a = {} // 在创建对象时,分配了一块内存,并把这块内存的地址传给 a 
m.set(a, 100) // 执行 set 操作时,实际上是将 a 指向的内存地址和 100 关联起来

const a = 'abc' // 由于基本数据类型在传递时,传递的是值,而不是引用。
m.set(a, 100) // 所以执行 set 操作时,实际上是将新的 'abc' 和 100 关联起来,而不是原来 a 变量指向的那个。
	      // 那这样就会有问题,m 里存储的永远是没有被引用的键,随时都会被回收。

参考资料:

HTML

54. HTML5语义化

什么是语义化?就是用合理、正确的标签来展示内容,比如h1~h6定义标题。

好处

  • 易于用户阅读,样式丢失的时候能让页面呈现清晰的结构。
  • 有利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权重。
  • 方便其他设备解析,如盲人阅读器根据语义渲染网页
  • 有利于开发和维护,语义化更具可读性,代码更好维护,与CSS3关系更和谐。

55. 为什么最好把 CSS 的<link>标签放在<head></head>之间?为什么最好把 JS 的<script>标签恰好放在</body>之前,有例外情况吗?

<link>放在<head>

这种做法可以让页面逐步呈现,提高了用户体验。将样式表放在文档底部附近,会使许多浏览器(包括 Internet Explorer)不能逐步呈现页面。一些浏览器会阻止渲染,以避免在页面样式发生变化时,重新绘制页面中的元素。这种做法可以防止呈现给用户空白的页面或没有样式的内容。

<script>标签恰好放在</body>之前

脚本在下载和执行期间会阻止 HTML 解析。把<script>标签放在底部,保证 HTML 首先完成解析,将页面尽早呈现给用户。

如果一定要放在 <head> 中,可以让 <script> 标签使用 defer 属性。

56. 什么是渐进式渲染(progressive rendering)?

渐进式渲染是用于提高网页性能(尤其是提高用户感知的加载速度),以尽快呈现页面的技术。

在以前互联网带宽较小的时期,这种技术更为普遍。如今,移动终端的盛行,而移动网络往往不稳定,渐进式渲染在现代前端开发中仍然有用武之地。

一些举例:

  • 图片懒加载——页面上的图片不会一次性全部加载。当用户滚动页面到图片部分时,JavaScript 将加载并显示图像。
  • 确定显示内容的优先级(分层次渲染)——为了尽快将页面呈现给用户,页面只包含基本的最少量的 CSS、脚本和内容,然后可以使用延迟加载脚本或监听DOMContentLoaded/load事件加载其他资源和内容。
  • 异步加载 HTML 片段——当页面通过后台渲染时,把 HTML 拆分,通过异步请求,分块发送给浏览器。更多相关细节可以在这里找到。

57. viewport

Viewport :字面意思为视图窗口,在移动web开发中使用。表示将设备浏览器宽度虚拟成一个特定的值(或计算得出),这样利于移动web站点跨设备显示效果基本一致。移动版的 Safari 浏览器最新引进了 viewport 这个 meta tag,让网页开发者来控制 viewport 的大小和缩放,其他手机浏览器也基本支持。

在移动端浏览器当中,存在着两种视口,一种是可见视口(也就是我们说的设备大小),另一种是视窗视口(网页的宽度是多少)。 举个例子:如果我们的屏幕是320像素 * 480像素的大小(iPhone4),假设在浏览器中,320像素的屏幕宽度能够展示980像素宽度的内容。那么320像素的宽度就是可见视口的宽度,而能够显示的980像素的宽度就是视窗视口的宽度。

为了显示更多的内容,大多数的浏览器会把自己的视窗视口扩大,简易的理解,就是让原本320像素的屏幕宽度能够容下980像素甚至更宽的内容(将网页等比例缩小)。

Viewport属性值

  • width 设置layout viewport 的宽度,为一个正整数,或字符串"width-device"
  • initial-scale 设置页面的初始缩放值,为一个数字,可以带小数
  • minimum-scale 允许用户的最小缩放值,为一个数字,可以带小数
  • maximum-scale 允许用户的最大缩放值,为一个数字,可以带小数
  • height 设置layout viewport 的高度,这个属性对我们并不重要,很少使用
  • user-scalable 是否允许用户进行缩放,值为"no"或"yes", no 代表不允许,yes代表允许这些属性可以同时使用,也可以单独使用或混合使用,多个属性同时使用时用逗号隔开就行了。

58. Reflow和Repaint

Reflow

当涉及到DOM节点的布局属性发生变化时,就会重新计算该属性,浏览器会重新描绘相应的元素,此过程叫Reflow(回流或重排)。

Repaint

当影响DOM元素可见性的属性发生变化 (如 color) 时, 浏览器会重新描绘相应的元素, 此过程称为Repaint(重绘)。因此重排必然会引起重绘。

引起Repaint和Reflow的一些操作

  • 调整窗口大小
  • 字体大小
  • 样式表变动
  • 元素内容变化,尤其是输入控件
  • CSS伪类激活,在用户交互过程中发生
  • DOM操作,DOM元素增删、修改
  • width, clientWidth, scrollTop等布局宽高的计算

Repaint和Reflow是不可避免的,只能说对性能的影响减到最小,给出下面几条建议:

  • 避免逐条更改样式。建议集中修改样式,例如操作className。
  • 避免频繁操作DOM。创建一个documentFragment或div,在它上面应用所有DOM操作,最后添加到文档里。设置display:none的元素上操作,最后显示出来。
  • 避免频繁读取元素几何属性(例如scrollTop)。绝对定位具有复杂动画的元素。
  • 绝对定位使它脱离文档流,避免引起父元素及后续元素大量的回流

参考资料:

59. img中的alt和元素的title属性作用

  • img的alt属性
    如果无法显示图像,浏览器将显示alt指定的内容
  • 元素title属性
    在鼠标移到元素上时显示title的内容

60. href和src区别

href

href标识超文本引用,用在link和a等元素上,href是引用和页面关联,是在当前元素和引用资源之间建立联系
若在文档中添加href ,浏览器会识别该文档为 CSS 文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式加载 CSS,而不是使用 @import 方式。

src

src表示引用资源,替换当前元素,用在img,script,iframe上,src是页面内容不可缺少的一部分。
当浏览器解析到src ,会暂停其他资源的下载和处理(图片不会暂停其他资源下载和处理),直到将该资源加载、编译、执行完毕,图片和框架等也如此,类似于将所指向资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因。

参考资料:

61. 浏览器的渲染过程

  1. 解析HTML生成DOM树。
  2. 解析CSS生成CSSOM规则树。
  3. 将DOM树与CSSOM规则树合并在一起生成渲染树。
  4. 遍历渲染树开始布局,计算每个节点的位置大小信息。
  5. 调用 GPU 绘制,合成图层。
  6. 将渲染树每个节点绘制到屏幕。

62. 为何会出现浏览器兼容问题

  • 同一产品,版本越老 bug 越多
  • 同一产品,版本越新,功能越多
  • 不同产品,不同标准,不同实现方式

处理兼容问题的思路

  1. 要不要做
  • 产品的角度(产品的受众、受众的浏览器比例、效果优先还是基本功能优先)
  • 成本的角度 (有无必要做某件事)

2.做到什么程度

  • 让哪些浏览器支持哪些效果

3..如何做

  • 根据兼容需求选择技术框架/库(jquery)

  • 根据兼容需求选择兼容工具(html5shiv.js、respond.js、css reset、normalize.css、Modernizr)

  • 条件注释、CSS Hack、js 能力检测做一些修补

  • 渐进增强(progressive enhancement): 针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验

  • 优雅降级 (graceful degradation): 一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

参考资料:

63. doctype有什么用

doctype是一种标准通用标记语言的文档类型声明,目的是告诉标准通用标记语言解析器要使用什么样的文档类型定义(DTD)来解析文档。

声明是用来指示web浏览器关于页面使用哪个HTML版本进行编写的指令。 声明必须是HTML文档的第一行,位于html标签之前。

浏览器本身分为两种模式,一种是标准模式,一种是怪异模式。

浏览器通过doctype来区分这两种模式,doctype在html中的作用就是触发浏览器的标准模式,如果html中省略了doctype,浏览器就会进入到Quirks模式的怪异状态。

在这种模式下,有些样式会和标准模式存在差异。

而html标准和dom标准值规定了标准模式下的行为,没有对怪异模式做出规定,因此不同浏览器在怪异模式下的处理也是不同的,所以一定要在html开头使用doctype。

64. 行内元素和块级元素有哪些

行内元素

一个行内元素只占据它对应标签的边框所包含的空间
一般情况下,行内元素只能包含数据和其他行内元素

b, big, i, small, tt
abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var
a, bdo, br, img, map, object, q, script, span, sub, sup
button, input, label, select, textarea

块级元素

占据一整行,高度、行高、内边距和外边距都可以改变,可以容纳块级标签和其他行内标签

header,form,ul,ol,table,article,div,hr,aside,figure,canvas,video,audio,footer

65. 行内元素、块级元素区别

行内元素:和其他元素都在一行上,高度、行高及外边距和内边距都不可改变(边距上下方向不可改变,左右方向可以改变),文字图片的宽度不可改变,只能容纳文本或者其他行内元素;其中img是行元素

块级元素:总是在新行上开始,高度、行高及外边距和内边距都可控制,可以容纳内敛元素和其他元素;行元素转换为块级元素方式:display:block;

66. iframe框架有那些优缺点

优点:

  • iframe能够原封不动的把嵌入的网页展现出来。
  • 如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
  • 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
  • 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。

缺点:

  • 搜索引擎的爬虫程序无法解读这种页面
  • 框架结构中出现各种滚动条
  • 使用框架结构时,保证设置正确的导航链接。
  • iframe页面会增加服务器的http请求

67. label标签有什么作用

label 标签通常是写在表单内,它关联一个控件,使用 label 可以实现点击文字选取对应的控件。

<input type="checkbox" id="test">
<label for="test" >test</label>

68. HTML5的form如何关闭自动完成功能

将不想要自动完成的 forminput 设置为 autocomplete=off

69. DOM和BOM有什么区别

DOM

Document Object Model,文档对象模型

DOM 是为了操作文档出现的 API,document 是其的一个对象

DOM和文档有关,这里的文档指的是网页,也就是html文档。DOM和浏览器无关,他关注的是网页本身的内容。

BOM

Browser Object Model,浏览器对象模型

BOM 是为了操作浏览器出现的 API,window 是其的一个对象

window 对象既为 javascript 访问浏览器提供API,同时在 ECMAScript 中充当 Global 对象

CSS

CSS 部分内容引用自 front-end-interview-handbook

70. CSS 选择器的优先级是如何计算的?

浏览器通过优先级规则,判断元素展示哪些样式。优先级通过 4 个维度指标确定,我们假定以a、b、c、d命名,分别代表以下含义:

  1. a表示是否使用内联样式(inline style)。如果使用,a为 1,否则为 0。
  2. b表示 ID 选择器的数量。
  3. c表示类选择器、属性选择器和伪类选择器数量之和。
  4. d表示标签(类型)选择器和伪元素选择器之和。

优先级的结果并非通过以上四个值生成一个得分,而是每个值分开比较。a、b、c、d权重从左到右,依次减小。判断优先级时,从左到右,一一比较,直到比较出最大值,即可停止。所以,如果b的值不同,那么cd不管多大,都不会对结果产生影响。比如0,1,0,0的优先级高于0,0,10,10

当出现优先级相等的情况时,最晚出现的样式规则会被采纳。如果你在样式表里写了相同的规则(无论是在该文件内部还是其它样式文件中),那么最后出现的(在文件底部的)样式优先级更高,因此会被采纳。

在写样式时,我会使用较低的优先级,这样这些样式可以轻易地覆盖掉。尤其对写 UI 组件的时候更为重要,这样使用者就不需要通过非常复杂的优先级规则或使用!important的方式,去覆盖组件的样式了。

参考资料:

71. 请阐述Float定位的工作原理。

浮动(float)是 CSS 定位属性。浮动元素从网页的正常流动中移出,但是保持了部分的流动性,会影响其他元素的定位(比如文字会围绕着浮动元素)。这一点与绝对定位不同,绝对定位的元素完全从文档流中脱离。

CSS 的clear属性通过使用leftrightboth,让该元素向下移动(清除浮动)到浮动元素下面。

如果父元素只包含浮动元素,那么该父元素的高度将塌缩为 0。我们可以通过清除(clear)从浮动元素后到父元素关闭前之间的浮动来修复这个问题。

有一种 hack 的方法,是自定义一个.clearfix类,利用伪元素选择器::after清除浮动。另外还有一些方法,比如添加空的<div></div>和设置浮动元素父元素的overflow属性。与这些方法不同的是,clearfix方法,只需要给父元素添加一个类,定义如下:

.clearfix::after {
  content: '';
  display: block;
  clear: both;
}

值得一提的是,把父元素属性设置为overflow: autooverflow: hidden,会使其内部的子元素形成块格式化上下文(Block Formatting Context),并且父元素会扩张自己,使其能够包围它的子元素。

参考资料:

72. 请阐述z-index属性,并说明如何形成层叠上下文(stacking context)。

CSS 中的z-index属性控制重叠元素的垂直叠加顺序。z-index只能影响position值不是static的元素。

没有定义z-index的值时,元素按照它们出现在 DOM 中的顺序堆叠。非静态定位的元素(及其子元素)将始终覆盖静态定位(static)的元素,而不管 HTML 层次结构如何。

层叠上下文是包含一组图层的元素。 在一组层叠上下文中,其子元素的z-index值是相对于该父元素而不是 document root 设置的。每个层叠上下文完全独立于它的兄弟元素。如果元素 B 位于元素 A 之上,则即使元素 A 的子元素 C 具有比元素 B 更高的z-index值,元素 C 也永远不会在元素 B 之上.

每个层叠上下文是自包含的:当元素的内容发生层叠后,整个该元素将会在父层叠上下文中按顺序进行层叠。少数 CSS 属性会触发一个新的层叠上下文,例如opacity小于 1,filter不是nonetransform不是none

参考资料:

73. 请阐述块格式化上下文(Block Formatting Context)及其工作原理。

块格式上下文(BFC)是 Web 页面的可视化 CSS 渲染的部分,是块级盒布局发生的区域,也是浮动元素与其他元素交互的区域。

一个 HTML 盒(Box)满足以下任意一条,会创建块格式化上下文:

  • float的值不是none.
  • position的值不是staticrelative.
  • overflow的值不是visible

在 BFC 中,每个盒的左外边缘都与其包含的块的左边缘相接。

两个相邻的块级盒在垂直方向上的边距会发生合并(collapse)。更多内容请参考边距合并(margin collapsing)

参考资料:

74. 有哪些清除浮动的技术,都适用哪些情况?

  • div方法:<div style="clear:both;"></div>
  • Clearfix 方法:上文使用.clearfix类已经提到。
  • overflow: autooverflow: hidden方法:上文已经提到。

在大型项目中,我会使用 Clearfix 方法,在需要的地方使用.clearfix。设置overflow: hidden的方法可能使其子元素显示不完整,当子元素的高度大于父元素时。

75. 如何解决不同浏览器的样式兼容性问题?

  • 在确定问题原因和有问题的浏览器后,使用单独的样式表,仅供出现问题的浏览器加载。这种方法需要使用服务器端渲染。
  • 使用已经处理好此类问题的库,比如 Bootstrap。
  • 使用 autoprefixer 自动生成 CSS 属性前缀。
  • 使用 Reset CSS 或 Normalize.css。

76. 如何为功能受限的浏览器提供页面? 使用什么样的技术和流程?

  • 优雅的降级:为现代浏览器构建应用,同时确保它在旧版浏览器中正常运行。
  • Progressive enhancement - The practice of building an application for a base level of user experience, but adding functional enhancements when a browser supports it.
  • 渐进式增强:构建基于用户体验的应用,但在浏览器支持时添加新增功能。
  • 利用 caniuse.com 检查特性支持。
  • 使用 autoprefixer 自动生成 CSS 属性前缀。
  • 使用 Modernizr进行特性检测。

77. 有什么不同的方式可以隐藏内容(使其仅适用于屏幕阅读器)?

这些方法与可访问性有关。

  • visibility: hidden:元素仍然在页面流中,并占用空间。
  • width: 0; height: 0:使元素不占用屏幕上的任何空间,导致不显示它。
  • position: absolute; left: -99999px: 将它置于屏幕之外。
  • text-indent: -9999px:这只适用于block元素中的文本。
  • Metadata: 例如通过使用 Schema.org,RDF 和 JSON-LD。
  • WAI-ARIA:如何增加网页可访问性的 W3C 技术规范。

即使 WAI-ARIA 是理想的解决方案,我也会采用绝对定位方法,因为它具有最少的注意事项,适用于大多数元素,而且使用起来非常简单。

参考资料:

78. 编写高效的 CSS 应该注意什么?

首先,浏览器从最右边的选择器,即关键选择器(key selector),向左依次匹配。根据关键选择器,浏览器从 DOM 中筛选出元素,然后向上遍历被选元素的父元素,判断是否匹配。选择器匹配语句链越短,浏览器的匹配速度越快。避免使用标签和通用选择器作为关键选择器,因为它们会匹配大量的元素,浏览器必须要进行大量的工作,去判断这些元素的父元素们是否匹配。

BEM (Block Element Modifier) methodology recommends that everything has a single class, and, where you need hierarchy, that gets baked into the name of the class as well, this naturally makes the selector efficient and easy to override. BEM (Block Element Modifier)原则上建议为独立的 CSS 类命名,并且在需要层级关系时,将关系也体现在命名中,这自然会使选择器高效且易于覆盖。

搞清楚哪些 CSS 属性会触发重新布局(reflow)、重绘(repaint)和合成(compositing)。在写样式时,避免触发重新布局的可能。

参考资料:

79. 使用 CSS 预处理的优缺点分别是什么?

优点:

  • 提高 CSS 可维护性。
  • 易于编写嵌套选择器。
  • 引入变量,增添主题功能。可以在不同的项目中共享主题文件。
  • 通过混合(Mixins)生成重复的 CSS。
  • Splitting your code into multiple files. CSS files can be split up too but doing so will require a HTTP request to download each CSS file.
  • 将代码分割成多个文件。不进行预处理的 CSS,虽然也可以分割成多个文件,但需要建立多个 HTTP 请求加载这些文件。

缺点:

  • 需要预处理工具。
  • 重新编译的时间可能会很慢。

80. 对于你使用过的 CSS 预处理,说说喜欢和不喜欢的地方?

喜欢:

  • 绝大部分优点上题以及提过。
  • Less 用 JavaScript 实现,与 NodeJS 高度结合。

Dislikes:

  • 我通过node-sass使用 Sass,它用 C ++ 编写的 LibSass 绑定。在 Node 版本切换时,我必须经常重新编译。
  • Less 中,变量名称以@作为前缀,容易与 CSS 关键字混淆,如@media@import@font-face

81. 如何实现一个使用非标准字体的网页设计?

使用@font-face并为不同的font-weight定义font-family

82. 解释浏览器如何确定哪些元素与 CSS 选择器匹配。

这部分与上面关于编写高效的 CSS 有关。浏览器从最右边的选择器(关键选择器)根据关键选择器,浏览器从 DOM 中筛选出元素,然后向上遍历被选元素的父元素,判断是否匹配。选择器匹配语句链越短,浏览器的匹配速度越快。

例如,对于形如p span的选择器,浏览器首先找到所有<span>元素,并遍历它的父元素直到根元素以找到<p>元素。对于特定的<span>,只要找到一个<p>,就知道'`已经匹配并停止继续匹配。

参考资料:

83. 描述伪元素及其用途。

CSS 伪元素是添加到选择器的关键字,去选择元素的特定部分。它们可以用于装饰(:first-line:first-letter)或将元素添加到标记中(与 content:...组合),而不必修改标记(:before:after)。

  • :first-line:first-letter可以用来修饰文字。
  • 上面提到的.clearfix方法中,使用clear: both来添加不占空间的元素。
  • 使用:beforeafter展示提示中的三角箭头。鼓励关注点分离,因为三角被视为样式的一部分,而不是真正的 DOM。如果不使用额外的 HTML 元素,只用 CSS 样式绘制三角形是不太可能的。

参考资料:

84. 说说你对盒模型的理解,以及如何告知浏览器使用不同的盒模型渲染布局。

CSS 盒模型描述了以文档树中的元素而生成的矩形框,并根据排版模式进行布局。每个盒子都有一个内容区域(例如文本,图像等)以及周围可选的paddingbordermargin区域。

CSS 盒模型负责计算:

  • 块级元素占用多少空间。
  • 边框是否重叠,边距是否合并。
  • 盒子的尺寸。

盒模型有以下规则:

  • 块级元素的大小由widthheightpaddingbordermargin决定。
  • 如果没有指定height,则块级元素的高度等于其包含子元素的内容高度加上padding(除非有浮动元素,请参阅下文)。
  • 如果没有指定width,则非浮动块级元素的宽度等于其父元素的宽度减去父元素的padding
  • 元素的height是由内容的height来计算的。
  • 元素的width是由内容的width来计算的。
  • 默认情况下,paddingborder不是元素widthheight的组成部分。

参考资料:

85. * { box-sizing: border-box; }会产生怎样的效果?

  • 元素默认应用了box-sizing: content-box,元素的宽高只会决定内容(content)的大小。
  • box-sizing: border-box改变计算元素widthheight的方式,borderpadding的大小也将计算在内。
  • 元素的height = 内容(content)的高度 + 垂直方向的padding + 垂直方向border的宽度
  • 元素的width = 内容(content)的宽度 + 水平方向的padding + 水平方向border的宽度

86. display的属性值都有哪些?

  • none, block, inline, inline-block, table, table-row, table-cell, list-item.

87. inlineinline-block有什么区别?

我把block也加入其中,为了获得更好的比较。

blockinline-blockinline
大小填充其父容器的宽度。取决于内容。取决于内容。
定位从新的一行开始,并且不允许旁边有 HTML 元素(除非是float与其他内容一起流动,并允许旁边有其他元素。与其他内容一起流动,并允许旁边有其他元素。
能否设置widthheight不能。 设置会被忽略。
可以使用vertical-align对齐不可以可以可以
边距(margin)和填充(padding)各个方向都存在各个方向都存在只有水平方向存在。垂直方向会被忽略。 尽管borderpaddingcontent周围,但垂直方向上的空间取决于'line-height'
浮动(float)--就像一个block元素,可以设置垂直边距和填充。

88. relativefixedabsolutestatic四种定位有什么区别?

经过定位的元素,其position属性值必然是relativeabsolutefixedsticky

  • static:默认定位属性值。该关键字指定元素使用正常的布局行为,即元素在文档常规流中当前的布局位置。此时 top, right, bottom, left 和 z-index 属性无效。
  • relative:该关键字下,元素先放置在未添加定位时的位置,再在不改变页面布局的前提下调整元素位置(因此会在此元素未添加定位时所在位置留下空白)。
  • absolute:不为元素预留空间,通过指定元素相对于最近的非 static 定位祖先元素的偏移,来确定元素位置。绝对定位的元素可以设置外边距(margins),且不会与其他边距合并。
  • fixed:不为元素预留空间,而是通过指定元素相对于屏幕视口(viewport)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变。打印时,元素会出现在的每页的固定位置。fixed 属性会创建新的层叠上下文。当元素祖先的 transform 属性非 none 时,容器由视口改为该祖先。
  • sticky:盒位置根据正常流计算(这称为正常流动中的位置),然后相对于该元素在流中的 flow root(BFC)和 containing block(最近的块级祖先元素)定位。在所有情况下(即便被定位元素为 table 时),该元素定位均不对后续元素造成影响。当元素 B 被粘性定位时,后续元素的位置仍按照 B 未定位时的位置来确定。position: stickytable 元素的效果与 position: relative 相同。

参考资料:

89. 你了解 CSS Flex 和 Grid 吗?

Flex 主要用于一维布局,而 Grid 则用于二维布局。

Flex

flex容器中存在两条轴, 横轴和纵轴, 容器中的每个单元称为flex item。

在容器上可以设置6个属性:

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

注意:当设置 flex 布局之后,子元素的 float、clear、vertical-align 的属性将会失效。

Flex 项目属性

有六种属性可运用在 item 项目上:

  1. order
  2. flex-basis
  3. flex-grow
  4. flex-shrink
  5. flex
  6. align-self

Grid

CSS网格布局用于将页面分割成数个主要区域,或者用来定义组件内部元素间大小、位置和图层之间的关系。

像表格一样,网格布局让我们能够按行或列来对齐元素。 但是,使用CSS网格可能还是比CSS表格更容易布局。 例如,网格容器的子元素可以自己定位,以便它们像CSS定位的元素一样,真正的有重叠和层次。

90. 响应式设计与自适应设计有何不同?

响应式设计和自适应设计都以提高不同设备间的用户体验为目标,根据视窗大小、分辨率、使用环境和控制方式等参数进行优化调整。

响应式设计的适应性原则:网站应该凭借一份代码,在各种设备上都有良好的显示和使用效果。响应式网站通过使用媒体查询,自适应栅格和响应式图片,基于多种因素进行变化,创造出优良的用户体验。就像一个球通过膨胀和收缩,来适应不同大小的篮圈。

自适应设计更像是渐进式增强的现代解释。与响应式设计单一地去适配不同,自适应设计通过检测设备和其他特征,从早已定义好的一系列视窗大小和其他特性中,选出最恰当的功能和布局。与使用一个球去穿过各种的篮筐不同,自适应设计允许使用多个球,然后根据不同的篮筐大小,去选择最合适的一个。

参考资料:

91. 你有没有使用过视网膜分辨率的图形?当中使用什么技术?

我倾向于使用更高分辨率的图形(显示尺寸的两倍)来处理视网膜显示。更好的方法是使用媒体查询,像@media only screen and (min-device-pixel-ratio: 2) { ... },然后改变background-image

对于图标类的图形,我会尽可能使用 svg 和图标字体,因为它们在任何分辨率下,都能被渲染得十分清晰。

还有一种方法是,在检查了window.devicePixelRatio的值后,利用 JavaScript 将<img>src属性修改,用更高分辨率的版本进行替换。

参考资料:

92. 什么情况下,用translate()而不用绝对定位?什么时候,情况相反。

translate()transform的一个值。改变transformopacity不会触发浏览器重新布局(reflow)或重绘(repaint),只会触发复合(compositions)。而改变绝对定位会触发重新布局,进而触发重绘和复合。transform使浏览器为元素创建一个 GPU 图层,但改变绝对定位会使用到 CPU。 因此translate()更高效,可以缩短平滑动画的绘制时间。

当使用translate()时,元素仍然占据其原始空间(有点像position:relative),这与改变绝对定位不同。

参考资料:

其他答案

93. 一边固定宽度一边宽度自适应

可以使用flex布局 复制下面的HTML和CSS代码 用浏览器打开可以看到效果

<div class="wrap">
  <div class="div1"></div>
  <div class="div2"></div>
</div>

.wrap {
  display: flex;
  justify-content: space-between;
}
.div1 {
  min-width: 200px;
}
.div2 {
  width: 100%;
  background: #e6e6e6;
}
html,
body,
div {
  height: 100%;
  margin: 0;
}

94. 水平垂直居中的方式

flex

// 父容器
display: flex;
justify-content: center;
align-items: center;

position

// 父容器
position: relative;

// 子容器
position:absolute;
margin:auto;
top:0;
bottom:0;
left:0;
right:0;

position+transform

// 父容器
position: relative;

// 子容器
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);

table-cell

<div class="box">
    <div class="content">
        <div class="inner"></div>
    </div>
</div>

html, body {
    height: 100%;
    width: 100%;
    margin: 0;
}
.box {
    display: table;
    height: 100%;
    width: 100%;
}
.content {
    display: table-cell;
    vertical-align: middle;
    text-align: center;
}
.inner {
    background-color: #000;
    display: inline-block;
    width: 200px;
    height: 200px;
}

95. display:none、visibile:hidden、opacity:0的区别

是否隐藏是否在文档中占用空间是否会触发事件
display: none
visibile: hidden
opacity: 0

96. CSS中link和@import的区别

  • link属于HTML标签,而@import是CSS提供的
  • 页面被加载的时,link会同时被加载,而@import引用的CSS会等到页面被加载完再加载
  • import只在IE5以上才能识别,而link是HTML标签,无兼容问题
  • link方式的样式的权重 高于@import的权重

97. 文本超出部分显示省略号

单行

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

多行

display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; // 最多显示几行
overflow: hidden;

98. 利用伪元素画三角

.info-tab {
    position: relative;
}
.info-tab::after {
    content: '';
    border: 4px solid transparent;
    border-top-color: #2c8ac2;
    position: absolute;
    top: 0;
}

99. 已知父级盒子的宽高,子级img宽高未知,想让img铺满父级盒子且图片不能变形

需要用到cssobject-fit属性

div {
    width: 200px;
    height: 200px;
}
img {
    object-fit: cover;
    width: 100%;
    height: 100%;
}

MDN

100. iframe的作用

iframe是用来在网页中插入第三方页面,早期的页面使用iframe主要是用于导航栏这种很多页面都相同的部分,这样在切换页面的时候避免重复下载。

优点

  1. 便于修改,模拟分离,像一些信息管理系统会用到。
  2. 但现在基本不推荐使用。除非特殊需要,一般不推荐使用。

缺点

  1. iframe的创建比一般的DOM元素慢了1-2个数量级
  2. iframe标签会阻塞页面的的加载,如果页面的onload事件不能及时触发,会让用户觉得网页加载很慢,用户体验不好,在Safari和Chrome中可以通过js动态设置iframe的src属性来避免阻塞。
  3. iframe对于SEO不友好,替换方案一般就是动态语言的Incude机制和ajax动态填充内容等。

101. 过渡与动画的区别是什么

  • transition
    可以在一定的时间内实现元素的状态过渡为最终状态,用于模拟以一种过渡动画效果,但是功能有限,只能用于制作简单的动画效果而动画属性
  • animation
    可以制作类似Flash动画,通过关键帧控制动画的每一步,控制更为精确,从而可以制作更为复杂的动画。

102. 什么是外边距合并

外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距。

合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。

103. 去除inline-block元素间间距的方法

  • 移除空格
  • 使用margin负值
  • 使用font-size:0
  • letter-spacing
  • word-spacing

更详细的介绍请看:去除inline-block元素间间距的N种方法

104. 为什么要初始化CSS样式

  • 因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对 CSS 初始化往往会出现浏览器之间的页面显示差异。
  • 去掉标签的默认样式如:margin,padding,其他浏览器默认解析字体大小,字体设置。

105. 行内格式化上下文 IFC

行内格式化上下文是一个网页的渲染结果的一部分。其中,各行内框(inline boxes)一个接一个地排列,其排列顺序根据书写模式(writing-mode)的设置来决定:

  • 对于水平书写模式,各个框从左边开始水平地排列
  • 对于垂直书写模式,各个框从顶部开始水平地排列

106. margin 塌陷

父子嵌套元素在垂直方向的 margin, 父子元素是结合在一起的, 他们两个的 margin 会取其中最大的值。

解决方法——触发 BFC

只要元素满足下面任一条件即可触发 BFC 特性:

  • body 根元素
  • 浮动元素:float 除 none 以外的值
  • 绝对定位元素:position (absolute、fixed)
  • display 为 inline-block、table-cells、flex
  • overflow 除了 visible 以外的值 (hidden、auto、scroll)

参考资料:

github 项目地址