前端面试题-Javascript篇

1. js中基础数据和引用数据类型有哪几种?了解包装对象么?

基础数据类型:String、Number、Boolean、null、undefined、Symbol(es6新增类型)

Undefined:声明变量但是没有初始化,这个变量的值就是undefined

Null类型只有一个值null,表示一个空对象指针,正式使用typeof操作符检测null会返回object

引用数据类型:Array、Object、function

包装对象:是当基础数据类型(String、Number、Boolean)以对象的方式去使用时,系统会自动转化为对象,相当于new一个对象

// let obj = “abc".split("")
function mySplit(str, method, arg) {
    let obj = new String(str);
    return obj[method](arg);
}
let str = "a b c";
let arr = mySplit(str, "split", " ");
console.log(arr)   // ["a", "b", "c"]

 

2. js判断类型

1. typeof 检测不出null和数组,结果都为object,所以typeof常用于检测基本类型
2. instanceof
不能检测出number、boolean、string、undefined、null、symbol类型,所以instancof常用于检测复杂类型以及级成关系
3. constructor
null、undefined没有construstor方法,因此constructor不能判断undefined和null。但是contructor的指向是可以被改变,所以不安全
4. Object.prototype.toString.call全类型都可以判断

3. 判断一个数组的方法

instanceof 判断一个对象是否是在其原型链原型构造函数上的属性

let arr = [];
console.log(arr instanceof Array); //true

constructor

let a = [1,3,4];
console.log(a.constructor)  //Array

Object.prototype.toString.call()

let a = [1,2,3]
console.log(Object.prototype.toString.call(a))  //[object Array]

Array.isArray() 用于确定传递的值是否是一个数组,返回一个布尔值。

let a = [1,2,3]
Array.isArray(a);//true

4. 数组常用的API

  1. join():拆分成字符串

  2. concat()方cheng法:数组拼接

  3. indexOf()/lastindexOf()方法:查找数组中的元素,返回数组下标

  4. reverse():反转数组元素

  5. slice()方法:截取数组的一部分,并返回一个新数组,不会改变原数组

  6. splice():它实现了对原数组进行删除、增加、替换的操作, 会改变原数组

  7. push():在数组末尾添加任意数量的元素,并返回修改后数组的长度

  8. pop():移除数组中末尾的元素,减少数组的 length 值,然后返回移除的项

  9. shift():删除数组中的第一项,并返回删除元素的值;如果数组为空则返回undefined

  10. unshift():将参数添加到原数组开头,并返回数组的长度

  11. forEach():对数组进行遍历循环,对数组中的每一项运行给定函数。这个方法没有返回值,参数都是function类型,默认有传参,参数分别为:遍历的数组内容;对应的数组索引,数组本身

  12. map():指“映射”,对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数

var arr = [1, 2, 3, 4, 5];
var arr2 = arr.map(item => item*item);
console.log(arr2); //[1, 4, 9, 16, 25]

   13. some():判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true

var arr = [1, 2, 3, 4, 5];
var arr2 = arr.some(x => x<3); 
console.log(arr2); //true

   14. every():判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回true

var arr = [1, 2, 3, 4, 5];
var arr3 = arr.every(x => x<3); 
console.log(arr3); // false

   15. filter():“过滤”功能,数组中的每一项运行给定函数,返回满足过滤条件组成的数组

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var arr2 = arr.filter((item, index) => {
  return index % 3 === 0 || item >= 8;
}); 
console.log(arr2); //[1, 4, 7, 8, 9, 10]

   16. Array.form():这个东西就是把一些集合,或者长的像数组的伪数组转换成真的数组,比如arguments,js选择器找到dom集合, 还有对象模拟的数组

   17. Array.of():把参数合并成一个数组返回,如果参数为空,则返回一个空数组

   18. find()+findIndex()返回数组中第一个符合条件的元素,findIndex返回索引

[1, 2, 3, 4, 5].find((item) => {return item > 3}) //4

    19. includes():判断数组是否包含某项,返回true/false

[1, 2, 3, 4, 5].includes(4)    //true
[1, 2, 3, 4, NaN].includes(6)    //false

  ​​​

5. let、var、const的区别

  1. var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。

  2. let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。

  3. var在js中是支持预解析的,而let不支持预解析

console.log(a)  // 打印undefined
var a = 22;

console.log(b)   // 报错:b is not defined
let b = 22

     4. var可以重复定义同一个变量,但是let不可以

var a = 100;
var a = 200;
console.log(a)    // 200
​
let a = 100;
let a = 200;
console.log(a)    // 报错:Identifier 'a' has already been declared

     5. var定义的全局变量会挂载到window对象上,使用window可以访问,let定义的全局变量则不会挂载到window对象上

var f = 200;
console.log(window.f)    // 200
​
let g = 200;
console.log(window.g)    // undefined

     6. const是用来定义常量的,常量定义之后是不允许改变的

     7. 用const定义常量必须赋值。不赋值的话会报错

6. js作用域的一般理解

全局作用域

  1. 全局作用域在页面打开时被创建,在页面关闭时被销毁

  2. 编写在script标签中的变量和函数,作用域为全局,在页面的任何位置都可以访问到

  3. 在全局作用域中有全局对象window,代表一个浏览器窗口,可以直接调用

  4. 全局作用域中声明的变量和函数会作为window对象的属性和方法保存

     全局变量:任何变量,未经声明而赋值,此变量为全局对象所有 a = 10 或 var a = b = 2

函数作用域

  1. 调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁

  2. 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的

  3. 在函数作用域中可以访问到全局作用域的变量,在函数之外无法访问到函数作用域内的变量

  4. 在函数作用域中访问变量、函数时,会先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域

7. js作用域的深层次理解

执行期的上下文

  • 当函数代码执行的前期,会创建一个执行期上下文的内容对象AO

  • 这个内部的对象是预编译的时候创建出来的,因为当函数被调用的时候,会先进行预编译

  • 在全局代码执行的前期会创建一个执行期的上下文的对象GO

函数作用域的预编译

  • 创建AO对象 AO{}

  • 找形参和变量声明,将变量和形参名,当作AO对象的属性名,值为undefined

  • 实参形参相统一

  • 在函数体里面找函数声明,值赋予函数体

function test(a, b) {
    console.log(a);  // function a() {}
    console.log(b);  // un
    var b = 234;
    console.log(b);  // 234
    a = 123;
    console.log(a);  // 123
    function a() {}
    var a;
    b = 234;
    var b = function() {}
    console.log(a);  // 123
    console.log(b);  // fn
}
test(1)
// 解析
/* AO {
 	 a: undefined -> 1 -> function a() {} -> 123
 	 b: undefined -> 234 -> fn
   }
*/

全局作用域的预编译

  • 创建GO对象

  • 找变量声明,将变量名作为GO对象的属性名,值为undefined

  • 找函数声明,值赋予函数体

8. js的严格模式

设立"严格模式"的目的,主要有以下几个:

  • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;

  • 消除代码运行的一些不安全之处,保证代码运行的安全;

  • 提高编译器效率,增加运行速度;

  • 为未来新版本的Javascript做好铺垫。

进入"严格模式"的标志是 "use strict"

调用的两种方式:

  1. 整个脚本文件,在文件的第一行写入 "use strict",整个文件以严格模式运行

  2. 针对单个函数,将"use strict"放在函数体的第一行,则整个函数以"严格模式"运行

严格模式的限制:

  1. 不允许使用未声明的变量

  2. 禁止使用width语句

  3. 禁止this关键字指向全局对象

  4. 禁止在函数内部遍历调用栈

  5. 函数不能有重复的参数

  6. 对象不能有重名的属性

  7. 不允许对arguments赋值

9. JS事件的委托机制

JS事件的委托机制是基于JS的事件冒泡,给父元素绑定事件,再通过事件冒泡传递到子元素上

目的:为了减少对DOM的操作

优点:

  1. 绑定同类子元素时,不需要循环绑定事件

  2. 动态添加了相同类型的子元素,如果采用事件委托机制,新元素也会被相同的事件监听到,而采用传统的事件绑定,则新元素上并没有添加相同事件的监听。

HTML代码:

<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>

传统的事件监听

window.onload=function() {
    var myUl = document.getElementById('ul');
    var lists = document.getElementsByTagName('li');
    for(var i = 0; i < lists.length; i++) {
        lists[i].addEventListener('click', function() {
            this.style.backgroundColor = '#66da70'
        })
    }
}

事件委托监听

window.onload = function() {
    var myUl = document.getElementById('ul');
    myUl.addEventListener('click', function(e) {
        var ev = e || window.e;   // ul点击事件的ev.target指向的是ul的子元素li
        var target = ev.target || ev.srcElement;  // 浏览器的兼容
        if(target.nodeName.toLowerCase() == 'li') {
            target.style.backgroundColor =  '#66da70'
        }
    })
}

 

10. js的防抖和节流

函数防抖(debounce):就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。

缺点:如果事件在规定的时间间隔内被不断的触发,则调用方法会被不断的延迟

function debounce(fn, delay) {
    let timer = null;   // 创建一个标记来存放定时器的返回值
    return function () {  // 使用闭包函数做缓存
        if (timer) clearTimeout(timer);   // 每当用户进行操作时,清除之前的定时器
        // 创建新的定时器
        timer = setTimeout(() => {
            fn.apply(this, arguments)
        }, delay)
    }
}
// 处理函数
function handle() {
    console.log('防抖', Math.random())
}
// 监听滚动事件
window.addEventListener('scroll', debounce(handle, 3000))

函数节流:高频事件触发,但在n秒内只会执行一次(每次触发事件时都判断当前是否有等待执行的延时函数)

function throttle(fn, delay) {
    let isBool = true;   // 通过闭包来保存一个标记
    return function() {
        if(!isBool) return;   // 通过判断标记是否为true,为false则return
        isBool = false;    // 再立即设置为false
        setTimeout(() => {   // 将外部传入的函数执行放在setTimeout中
            fn.apply(this, arguments);
            isBool = true;   // 最后在setTimeout执行完毕时,再给标记设置为true,表示可以执行下一次的操作了
        }, delay)
    }
}
function handle() {
    console.log('节流', Math.random())
}
document.getElementById('button').addEventListener('click', throttle(handle, 2000))

函数防抖和节流的区别:函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。

11. 深拷贝与浅拷贝的区别?如何实现深拷贝

浅拷贝:对于字符串来说,浅拷贝是对值的复制,对于对象来说,浅拷贝是对对象地址的复制,也就是两个对象指向同一个地址

深拷贝:是开辟了一个新的栈,两个对象对应两个不同的栈,修改一个对象的属性,不会改变另一个对象的属性

实现深拷贝的方法

     1. 用Json.stringify把对象转换成字符串,再用Json.parse把字符串转换成新的对象

JSON.parse(JSON.stringify(obj))

     2. 递归

function deepClone(obj) {
    let target;
    if (typeof obj === 'Object') {
        target = Array.isArray ? [] : {}
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                if (typeof obj[key] !== 'Object') {
                    target[key] = obj[key]
                } else {
                    target[key] = deepClone(obj[key])
                }
            }
        }
    } else {
        target = obj
    }
    return target
}
deepClone(obj)
obj = {
    a: 1,
    b: 2
}

     3. 使用jquery的extend方法

$.extend( deep是否深拷贝, target目标对象, object1源对象)
b=$.extend(true,[],a);

 

12. 箭头函数和普通函数的区别

  • 箭头函数是匿名函数,不能作为构造函数,不能使用new。

  • 箭头函数不绑定arguments,取而代之用rest参数...解决

  • 箭头函数没有this指向,会捕获其所在的上下文的this值,作为自己的this值

  • 不能通过bind、apply、call改变this指向

  • 箭头函数没有原型属性

  • 箭头函数不能当做Generator函数,不能使用yield关键字

13. 说说你对Promise的了解

  • Promise是为解决异步处理回调金字塔而产生的
  • 有三种状态,pengding、resolve、reject
  • then接受resolve(),catch接收reject()

14. 手写实现promise.all方法

let p1 = new Promise((reslove, reject) => {
    setTimeout(() => {
         console.log(2);
    }, 1000)
})
let p2 = new Promise((reslove, reject) => {
    setTimeout(() => {
        console.log(4);
    }, 2000)
})
/* 
Promise.all([p1,p2]).then(res => {
    console.log(res)
})
*/
// 手写实现Promise.all方法
function myPromiseAll(lists) {
    return new Promise((reslove, reject) => {
        if(!Array.isArray(lists)) {
            return reject(new TypeError('arguments must be array'))
        }
        let resArr = [];
        let num = 0;
        lists.forEach(item => {
            item.then(res => {
                resArr.push(item);
                num++;
                if(num === lists.length) {
                    reslove(resArr)
                }
            })
            
        })
    })
}
myPromiseAll([p1,p2]).then(res => {
    console.log(res)
})

15. Promise.all和Promise.race的区别和使用

Promise.all 将多个Promise实例包装成一个新的Promise实例,成功之后返回一个结果数组,失败则返回最先被reject失败状态的值

Promise.race只会返回一个执行速度最快的那个promise对象返回的结果,其他的异步函数照样还是会执行的 只是 不会再 执行 resolve和reject 也不会返回结果了

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值