首页 > 其他 > 详细

第十八章-面试真题

时间:2020-09-12 18:57:36      阅读:50      评论:0      收藏:0      [点我收藏+]

1. 何为变量提升?

1.1 var 和 let const 的区别?
  • var 是 ES5 语法,let 和 const ES6 语法
  • var 存在变量提升的情况(可以先使用再赋值),let 和 const 不存在变量提升
  • var 和 let 是变量,可修改;const 是常量,必须赋初始值而且不可修改
  • let const 有块级作用域,有暂时性死区的特性,var 没有块级作用域

使用 var 定义的变量或者函数表达式会存在变量提升的情况:将变量或者函数的定义提升到代码块的顶部,但不会提升赋值。

console.log(a) // 输出 undefined。原因是使用 var 定义的变量存在变量提升的情况
var a = 10
-----相当于下面的代码-----
var a // 将变量的定义提升到了代码块的顶部
console.log(a)
a = 10

// 块级作用域
for (var i = 0; i < 10; i++) {
	var j = i + 1
}
console.log(i, j) // 输出 10, 10
// 换成 let 就会报错 -- 原因:let 存在块级作用域
for (let i = 0; i < 10; i++) {
	let j = i + 1
}
console.log(i, j) // 报错
1.2 typeof 返回哪些类型?
  • 值类型:undefined string number boolean symbol
  • 引用类型:object (注意 typeof null === ‘object‘ )
  • 函数:function
1.3 列举强制类型转换和隐式类型转换?

强制类型转换:parseInt() parseFloat() toString()

隐式类型转换:if、逻辑判断、==、 + 字符串拼接

2. 手写深度比较 isEqual

2.1 手写深度比较,模拟 lodash 的 isEqual
		// 判断传过来的参数是不是一个对象
        function isObject(obj) {
            return typeof obj === ‘object‘ && obj !== null
        }

        function isEqual(obj1, obj2) {
            // 如果有一个不是对象,那么就直接判断就好了
            if (!isObject(obj1) || !isObject(obj2)) {
                // 参与 equal 的一般不会是函数
                return obj1 === obj2
            }
            // 如果传递过来的参数是 同一个对象,直接返回 true 
            if (obj1 === obj2) {
                return true
            }

            // 如果两个都是对象或者数组,而且不是同一个参数
            // 1. 先取出 obj1 和 obj2 的 keys ,比较个数
            const obj1Keys = Object.keys(obj1)
            const obj2Keys = Object.keys(obj2)
            if (obj1Keys.length !== obj2Keys.length) {
                return false
            }   
            // 2. 以 obj1 为基准,和 obj2 依次递归比较
            for (let key in obj1 ) {
                // 比较当前 key 的 value --递归
                const res = isEqual(obj1[key], obj2[key])
                if (!res) {
                    return false
                }
            }
            // 3. 全相等
            return true
        }
        
        const obj1 = {
            a: 100,
            b: { x: 10,y: 20 }
        }
        const obj2 = {
            a: 100,
            b: { x: 10,y: 20 }
        }
        // console.log(isEqual(obj1, obj1))
        const arr1 = [1,2,3,4]
        const arr2 = [1,2,3,4]
        console.log(isEqual(arr1, arr2))
2.2 split() 和 join() 的区别

split() 将字符串以某个字符分割成数组

join() 将数组以某个字符组合成一个字符串

‘1-2-3‘.split(‘-‘) // [1,2,3]
[1,2,3].join(‘-‘) // ‘1-2-3‘
2.3 数组的 pop push unshift shift 分别做什么

参考:https://aurorablog.top/archives/ec6bfcc0.html

功能是什么?

返回值是什么?

是否会对原数组造成影响?

方法 功能 返回值 是否影响原数组
pop 删除数组的最后一个值 返回被删除的那个值
push 在数组最后增加一项 返回增加一项后的数组长度
unshift 在数组最前面增加一项 返回增加一项后的数组长度
shift 删除数组的第一个值 返回删除的那个值

数组的 api 有那些事纯函数?

纯函数:1. 不改变原数组(改变原函数有副作用) 2. 返回一个数组

const arr = [1, 2, 3, 4, 5]
// concat 
const arr1 = arr.concat([6, 7, 8])
console.log(arr1) // [1, 2, 3, 4, 5, 6, 7, 8]

// map
const arr2 = arr.map(num => num * 10) 
console.log(arr2) // [10, 20, 30, 40, 50]

// filter
const arr3 = arr.filter(num => num > 2)
console.log(arr3) // [3, 4, 5]

// slice 参数是索引,截取一个新的数组
const arr4 = arr.slice(1, 3)
console.log(arr4) // [2, 3]
const arr5 = arr.slice()
console.log(arr5) // [1, 2, 3, 4, 5]

非纯函数:

  • pop push unshift shift
  • forEach
  • some every
  • reduce

3. 数组 map 方法

3.1 slice() 和 splice() 方法的区别?

功能区别:

? 英文含义:slice - 切片; splice - 剪接

? slice:截取数组的某一个片段

? splice:剪接原函数的某一部分,返回值是剪接的部分,原函数修改为去除被剪接的部分。

参数和返回值:

? slice():如果不传入参数,则相当于将原数组复制了一份;可以接受两个参数,第一个参数是 startIndex ,第二个参数是 endIndex ,左闭右开;如果没有第二个参数则会将 startIndex 后面所有的内容截取。如果想要截取最后几个数,可以写成 slice(-num) 意思是截取最后的 num 个内容。

const arr = [10, 20, 30, 40, 50]
const arr1 = arr.slice() // [10, 20, 30, 40, 50]
const arr2 = arr.slice(1, 4) // [20, 30, 40]
const arr3 = arr.slice(2) // [30, 40, 50]
const arr3 = arr.slice(-2) // [40, 50]

? splice():第一个参数是开始的位置,第二个参数的剪接的个数,第三个参数是要替换的元素值。返回值是被剪接的内容。

const arr = [1, 2, 3, 4, 5]
// 返回值 arr :[2, 3] 原数组 arr 变为: [1, ‘a‘, ‘b‘, 4, 5]
const arr1 = arr.splice(1, 2, ‘a‘, ‘b‘)
// 从索引为 1 的位置插入两项内容,arr:[1, "a", "b", 2, 3, 4, 5]
const arr2 = arr.splice(1, 0, ‘a‘, ‘b‘) // 返回值为空
// 从索引为 1 的位置删除两项内容,arr:  [1, 4, 5]
const arr3 = arr.splice(1, 2) // 返回值为 [2, 3]

是否是纯函数:

? slice() 是纯函数, splice() 不是纯函数。

3.2 [10, 20, 30].map(parseInt) 返回的结果是什么?

map 的参数和返回值:

map() 方法定义在JavaScript的Array中, 它返回一个新的数组, 数组中的元素为原始数组调用函数处理后的值。

array.map(function (currentValue, index, arr), thisIndex).

callback()

? currentValue: 必须。 当前元素的的值。
? index: 可选。 当前元素的索引。
? arr: 可选。 当前元素属于的数组对象。
thisIndex 可选。 对象作为该执行回调时使用, 传递给函数, 用作 "this"的值。

parseInt 的参数和返回值:

parseInt(string, radix) 解析一个字符串并返回指定基数的十进制整数, radix 是2-36之间的整数,表示被解析字符串的基数。

string:要被解析的字符串,如果不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。

radix:可选。该值介于 2~36 之间。如果省略该参数或其值为 0, 则数字将以 10 为基础来解析。 如果它以“ 0x” 或“ 0X” 开头, 将以 16 为基数。如果该参数小于 2 或者大于 36, 则 parseInt() 将返回 NaN

代码拆解:

[10, 20, 30].map((num,index)=>{
    return parseInt(num,index)
})

问题解析:

parseInt方法有两个参数,默认接受了来自map方法的前两个参数,map的前两个参数分别是遍历的值和索引;
所以parseInt接收到的三个组值得情况分别是:

parseInt(10,0):数字基数为0,数字以 10进制解析,故结果为 10;
parseInt(20,1):数字基数为1,数字以 1进制解析,1进制出现了2,1进制无法解析,结果返回NaN;
parseInt(30,2):数字基数为2,数字以 2进制解析,2进制出现了3,3进制无法解析,结果返回NaN;

所以最终结果为:[10, NaN, NaN]。参考 MDN这篇文章

ajax 请求 get 和 post 的区别?
  • get 一般用于查询操作,post 一般用于用户提交操作
  • get 参数拼接在 url 上(可能会受限于某些浏览器对 url 长度的限制),post 参数放在请求体内(数据体积会更大)
  • 安全性:post 易于防止 CSRF

参考:Ajax中POST和GET的区别

4. 再学闭包

4.1 函数 call 和 apply 的区别?

call() 和 apply() 的作用相同,都可以改变 this 的指向,但是参数有所不同。第一个参数都是 作为函数上下文的对象,call() 的第二个参数是一个参数列表;apply() 的第二个参数是一个数组或者类数组。

func.apply(obj, [‘A‘, ‘B‘]);
func.call(obj, ‘C‘, ‘D‘);

用法:

  1. 改变 this 指向

    var obj = {
        name: ‘linxin‘
    }
    function func() {
        console.log(this.name);
    }
    func.call(obj);
    

    call 方法的第一个参数是作为函数上下文的对象,这里把 obj 作为参数传给了 func,此时函数里的 this 便指向了 obj 对象。此处 func 函数里其实相当于

    function func() {
        console.log(obj.name);
    }
    
  2. 借用别的对象的方法

    var Person1  = function () {
        this.name = ‘linxin‘;
    }
    var Person2 = function () {
        this.getname = function () {
            console.log(this.name);
        }
        Person1.call(this);
    }
    var person = new Person2();
    person.getname();       // linxin
    

    从上面我们看到,Person2 实例化出来的对象 person 通过 getname 方法拿到了 Person1 中的 name。因为在 Person2 中,Person1.call(this) 的作用就是使用 Person1 对象代替 this 对象,那么 Person2 就有了 Person1 中的所有属性和方法了,相当于 Person2 继承了 Person1 的属性和方法。

  3. 调用函数

    apply、call 方法都会使函数立即执行,因此它们也可以用来调用函数。

    function func() {
        console.log(‘linxin‘);
    }
    func.call();// linxin
    

call 和 bind 的区别

在 EcmaScript5 中扩展了叫 bind 的方法,在低版本的 IE 中不兼容。它和 call 很相似,接受的参数有两部分,第一个参数是是作为函数上下文的对象,第二部分参数是个列表,可以接受多个参数。
它们之间的区别有以下两点。

  1. bind() 的返回值是函数。

    var obj = {
        name: ‘linxin‘
    }
    function func() {
        console.log(this.name);
    }
    var func1 = func.bind(obj);
    func1();  // linxin
    

    bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 func 中的 this 并没有被改变,依旧指向全局对象 window。

  2. 参数的使用

    function func(a, b, c) {
        console.log(a, b, c);
    }
    var func1 = func.bind(null,‘linxin‘);
    
    func(‘A‘, ‘B‘, ‘C‘);            // A B C
    func1(‘A‘, ‘B‘, ‘C‘);           // linxin A B
    func1(‘B‘, ‘C‘);                // linxin B C
    func.call(null, ‘linxin‘);      // linxin undefined undefined
    

    call 是把第二个及以后的参数作为 func 方法的实参传进去,而 func1 方法的实参实则是在 bind 中参数的基础上再往后排。

4.2 事件代理(事件委托)是什么?

事件捕获和事件冒泡都是为了解决页面中事件流(事件的执行顺序)的问题。

<div id="outer">
    <p id="inner">Click me!</p>
</div>

事件捕获:事件从最外层触发,直到找到最具体的元素。

如上面的代码,在事件捕获下,如果点击p标签,click事件的顺序应该是 document->html->body->div->p

事件冒泡:事件会从最内层的元素开始发生,一直向上传播,直到触发document对象。

因此在事件冒泡下,p元素发生click事件的顺序为 p->div->body->html->document

事件绑定:

js通过 addEventListener 绑定事件。 addEventListener 的第三个参数就是为冒泡和捕获准备的。

addEventListener 有三个参数:

element.addEventListener(event, function, useCapture)

第一个参数是:需要绑定的事件

第二个参数是:触发事件后要执行的函数

第三个参数是:默认值是 false,表示在 事件冒泡阶段 调用事件处理函数;如果设置参数为 true,则表示在 事件捕获阶段 调用事件处理函数。

事件代理(事件委托)

对于事件代理来说,在事件捕获或者事件冒泡阶段处理并没有明显的优劣之分,但是由于事件冒泡的事件流模型被所有主流的浏览器兼容,从兼容性角度来说通常使用事件冒泡模型。

事件代理的好处(为什么要使用事件代理)?

比如100个(甚至更多)li标签绑定事件,如果一个一个绑定,不仅会相当麻烦,还会占用大量的内存空间,降低性能。使用事件代理的作用如下:

  • 代码简洁
  • 减少浏览器内存占用

事件代理的原理:

事件代理(事件委托) 是利用事件的冒泡原理来实现的,比如当我们点击内部的li标签时,会冒泡到外层的ul,div等标签上。因此,当我们想给很多个li标签添加同一个事件的时候,可以给它的父级元素添加对应的事件,当触发任意li元素时,会冒泡到其父级元素,这时绑定在父级元素的事件就会被触发,这就是事件代理(委托),委托他们的父级代为执行事件。

demo

<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>
<script>
	window.onload = function(){
    	var oUl = document.getElementById("ul1");
   		oUl.onclick = function(){
        	alert(123);
    	}
	}
</script>

封装通用的事件绑定函数

// 通用的事件绑定函数:
function bindEvent(elem,type,selector,fn){
    if(fn == null){
      fn = selector
      selector = null
    }
    elem.addEventListener(type,event=>{
      const target = event.target
      if(selector){
          //代理绑定
          if(target.matches(selector)){
            fn.call(target,event)
           }else{
           	//普通绑定
           	fn.call(target,event)
           }
      }
    })
}
4.3 闭包是什么,有什么特性?有什么负面影响?

影响:变量会常驻内存,得不到释放。

闭包函数:声明在一个函数中的函数,叫做闭包函数。

闭包:内部函数总是可以访问到其所在的外部函数中,声明的参数和变量,即使是在其外部函数被返回之后。

函数内部定义函数,并且这个内部函数能够访问到外层函数中定义的变量,就叫做闭包

特点

1、让外部访问函数内部变量成为可能。

2、局部变量会常驻在内存中。

3、可以避免使用全局变量,防止全局变量污染。

4、会造成内存泄漏(内存空间长期被占用,而不被释放)

闭包的创建

闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中又被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象

闭包使用场景:防抖函数

5. 回顾 DOM 操作和优化

5.1 阻止事件冒泡和默认行为
// 阻止默认
event.stopPropagation()
// 阻止默认行为
event.preventDefault()
5.2 DOM 操作

6. jsonp 是 ajax 吗

6.1 解释 jsonp 的原理,为何 jsonp 不是跨域?

jsonp 是通过 script 标签来实现跨域的(script 标签默认可以访问外域链接),而 ajax 是通过 XMLHttpRequest 这个 api 实现的。

同源策略:协议、域名、端口号都相同

加载图片 css js 可以无视同源策略

  • <img src=跨域的图片地址> 如果引用的图片网站做了图片的防盗链,那么浏览器就无法加载这个图片
  • <link href=跨域的css地址>
  • <script src=跨域的js地址><script>
  • <img /> 可用于统计打点,可使用第三方统计服务
  • <link /> 和 <script> 可使用 CDN,CDN一般都是外域
  • <script> 可实现 JSONP

跨域实现

6.2 document load 和 ready 的区别
window.addEventListener(‘load‘, function() {
    // 页面的全部资源加载完才会执行,包括图片、视频等
})

document.addEventListener(‘DOMContentLoaded‘, function () {
    // DOM 渲染完成即可执行,此时图片、视频可能没有加载完毕
})
6.3 == 和 === 的区别
  • == 会尝试进行类型转换

  • === 严格相等

  • 那些场景下用 == :除了 == null 都用 ===

    • const obj = {x: 10}
      if (obj.a == null) {
      	// 相当于
          // if (obj.a === null || obj.a === null) {}
      }
      

7. 是否使用过 Object.create()

7.1 函数声明和函数表达式的区别

函数声明:function fn() {...}

函数表达式:const fn = function () {...}

函数声明会在代码执行前预加载,而函数表达式不会

// 函数声明
var res = sum(10, 20)
console.log(res) // 30

function sum(num1, num2) {
    return num1 + num2
}
----------------------
// 函数表达式
var res = sum(10, 20)
console.log(res) // 报错:sum 不是一个函数

var sum = function(num1, num2) {
    return num1 + num2
}
7.2 new Object() 和 Object.create() 的区别
  • {} 等同于 new Object() , 原型是 Object.prototype
  • Object.create(null) 没有原型
  • Object.create({...}) 可以指定原型

Object.create() 创建一个空对象,Object.create(obj) 把创建的空对象的原型设置为 obj

8. 常见的正则表达式

8.1 判断字符串以字母开头,后面字母数字下划线,长度 6-30
	/^[a-zA-Z]\w{5, 29}$/ 

正则表达式

8.2 作用域和自由变量的场景题
let i
for (i = 1; i <=3; i++) {
    setTimeout(function() { // 异步代码
        console.log(i)
    }, 0)
}
// 输出:4 4 4


let a = 100
function test() {
    alert(a)
    a = 10
    alert(a)
}
test() // 100 10
alert(a) // 10

9. 如何获取最大值

9.1 手写字符串 trim 保证浏览器兼容性
String.prototype.trim = funciton() {
    return this.replace(/^\s+/, ‘‘).replace(/\s+$/, ‘‘)
}
9.2 如何获取一组数中的最大值
function max() {
    nums = Array.prototype.slice.call(arguments) // 变为数组
    let max = 0
    nums.forEach(num => {
        if (num > max) {
            max = num
        }
    });
    return max
}
max(1, 2, 3, 4, 5) // 5

使用 Math.max() 方法

9.3 如何使用 js 实现继承
  • class 继承
  • prototype 继承

10. 解析 URL 参数

10.1 如何捕获 js 中的异常
try {
    // todo
} catch(ex) {
    // 手动捕获异常
    console.log(ex)
} finally {
	// todo
}

// 自动捕获
window.onerror = function (message, source, lineNum, colNum, error) {
    // 1. 对于跨域的 js ,如 cdn 不会有详细的报错信息
    // 2. 对于压缩的 js ,还要配合 sourceMap 反查到未压缩代码的行和列
}
10.2 什么是 JSON
  • json 是一种数据格式,本质上是一段字符串。
  • json 格式和 js 对象结构一致,对 js 语言更加友好
  • window.JSON 是一个全局对象:JSON.stringify 和 JSON.parse
10.3 获取当前页面 URL 的参数
// 传统方式
function query(name) {
    const search = location.search.substr(1)
    // search: ‘a=10&b=20&c=30‘
    const reg = new RegExp(`(^|&)${name}=([^&*])(&|$)`, ‘i‘)
    const res = search.match(reg)
    console.log(res)
    if (res === null) {
		return null
    }
    return res[2]
}
query(‘a‘) // 10

// URLSearchParams
function query(name) {
    const search = location.search
    const p = new URLSearchParams(search)
    return p.get(name)
}
console.log(query(‘a‘)) // 10

11. 数组去重

11.1 将 url 参数转换成 js 对象
// 传统方式,分析 search
function queryToObj() {
    const res = {}
    const search = location.search.substr(1) // 去除 ? 
    search.split(‘&‘).forEach( paramsStr => {
        const arr = paramsStr.split(‘=‘)
        const key = arr[0]
        const value = arr[1]
        res[key] = value
    })
    return res
}

// URLSearchParams
function queryToObj() {
    const res = {}
    const paramsList = new URLSearchParams(location.search)
    paramsList.forEach((val, key) => {
        res[key] = val
    })
    return res
}
11.2 手写 flatern ,考虑多层级(把多层嵌套数组拍平成为一个数组)
function flat(arr) {
    // 判断 arr 中有没有深层数组
    const isDeep = arr.some(item => item instanceof Array)
    // 如果没有深层数组,直接返回
    if (!isDeep) {
        return arr
    }
    const res = Array.prototype.concat.apply([], arr)
    return flat(res) // 递归
}
const res = flat( [1, 2, [10, 20, [300,400]]] )
console.log(res)
11.3 数组去重
  • 传统方式:遍历元素,挨个比较
  • 使用 Set
  • 考虑计算效率:数据量很大的时候使用 Set 效率更高
function unique(arr) {
    const res = [] 
    arr.forEach(item => {
        if (res.indexOf(item) < 0) {
            res.push(item)
        }
    })
    return res
}
const res = unique([1,1,,1,1,2,3,3,3])
console.log(res)

// 使用 Set
function unique(arr) {
    return new Set(arr)
}
const res = unique([1, 1, 2, 3, 3, 2])
console.log(res)

12. 是否使用过 requestAnimationFrame(RAF)

12.1 手写深拷贝
function deepClone(obj) {
    if ( typeof obj !== ‘object‘ || obj == null) {
        // 如果 obj 不是对象,或者 obj 为 null 或者 undefined,直接返回
        return obj
    }
    let result
    if (obj instanceof Array) {
		result = []
    } else {
        result = {}
    }
    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            result[key] = deepClone(obj[key])
        }
    }
    // 返回结果
    return result
}
const res = deepClone({name: ‘aurora‘, age: 21, address: {province: ‘heilongjiang‘, city: ‘haerbin‘}})
console.log(res)

Object.assign() 不是深拷贝!!

12.2 是否使用过 requestAnimationFrame(RAF)
  • 想要动画流畅,更新频率要 60帧/s,即 16.7ms 更新一次视图
  • setTimeout 需要手动控制频率,而使用 RAF 浏览器会自动控制
  • 后台标签或者隐藏在 frame 标签中, RAF 会自动停止,但 setTimeout 不会自动停止
<style>
    #div1 {
		width: 100px;
        height: 50px;
        background-color: red;
    }
</style>
<div id=‘div1‘></div>

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
	// 3s 把宽度从 100px 变为 640px, 即增加 540px
    // 60帧/s 3s 180帧 每次增加 3px
    const $div = $(‘#div1‘)
    let curWidth = 100
    const maxWidth = 640
    
    // function animation() {
       // curWidth = curWidth + 3
       // $div1.css(‘width‘, curWidth)
       // if (curWidth < maxWidth) {
         //   // 自己控制时间
         //  setTimeout(animation, 16.7)
       // }
    // }
    
    function animation() {
        curWidth = curWidth + 3
        $div1.css(‘width‘, curWidth)
        if (curWidth < maxWidth) {
            window.requestAnimationFrame(animation)
        }
    }
    animation()
</script>
12.3 前端性能优化?一般从哪几个方面考虑?

原则:多使用内存、缓存,减少计算、减少网络请求

方向:加载页面,页面渲染,页面操作流畅度

第十八章-面试真题

原文:https://www.cnblogs.com/aurora-ql/p/13657820.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!