乐趣区

关于javascript:35道面向初中级前端的基础面试题

如需获取完整版 229 页 PDF 面试题,请间接滑到文末。

1. 什么是同源策略?

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

参考资料:

  • 浏览器的同源策略

2. 跨域是什么?

  • 起因

浏览器的同源策略导致了跨域

  • 作用

用于隔离潜在歹意文件的重要平安机制

  • 解决
  1. jsonp,容许 script 加载第三方资源
  2. 反向代理(nginx 服务外部配置 Access-Control-Allow-Origin *)
  3. cors 前后端合作设置申请头部,Access-Control-Allow-Origin 等头部信息
  4. iframe 嵌套通信,postmessage

参考资料:

  • 新鲜出炉的 8 月前端面试题
  • 跨域资源共享 CORS 阮一峰

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 的毛病。

参考资料:

  • 应用 Ajax 的优缺点别离是什么?

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 区别

个性 cookie localStorage sessionStorage
由谁初始化 客户端或服务器,服务器能够应用 Set-Cookie 申请头。 客户端 客户端
数据的生命周期 个别由服务器生成,可设置生效工夫,如果在浏览器生成,默认是敞开浏览器之后生效 永恒保留,可革除 仅在以后会话无效,敞开页面后革除
存放数据大小 4KB 5MB 5MB
与服务器通信 每次都会携带在 HTTP 头中,如果应用 cookie 保留过多数据会带来性能问题 仅在客户端保留 仅在客户端保留
用处 个别由服务器生成,用于标识用户身份 用于浏览器缓存数据 用于浏览器缓存数据
拜访权限 任意窗口 任意窗口 以后页面窗口

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

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

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

益处
  • 避免变量弥散到全局,免得各种 js 库抵触。
  • 隔离作用域防止净化,或者截断作用域链,防止闭包造成援用变量无奈开释。
  • 利用立刻执行个性,返回须要的业务函数或对象,防止每次通过条件判断来解决。
场景

个别用于框架、插件等场景

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

有如下几个形式:

  • cookie
  • web worker
  • localeStorage 和 sessionStorage

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

  1. 代码复杂度,js 动画代码绝对简单一些
  2. 动画运行时,对动画的管制水平上,js 可能让动画,暂停,勾销,终止,css 动画不能增加事件
  3. 动画性能看,js 动画多了一个 js 解析的过程,性能不如 css 动画好

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被设置为它被创立时的上下文。

我平时始终有整顿面试题的习惯,有随时跳出舒服圈的筹备,人不知; 鬼不觉整顿了 229 页了,在这里分享给大家,有须要的点击这里收费支付题目 + 解析 PDF

篇幅无限,仅展现局部内容

如果你须要这份完整版的面试题 + 解析,【点击我】就能够了。

心愿大家明年的金三银四面试顺利,拿下本人心仪的 offer!

退出移动版