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
join():拆分成字符串
concat()方cheng法:数组拼接
indexOf()/lastindexOf()方法:查找数组中的元素,返回数组下标
reverse():反转数组元素
slice()方法:截取数组的一部分,并返回一个新数组,不会改变原数组
splice():它实现了对原数组进行删除、增加、替换的操作, 会改变原数组
push():在数组末尾添加任意数量的元素,并返回修改后数组的长度
pop():移除数组中末尾的元素,减少数组的 length 值,然后返回移除的项
shift():删除数组中的第一项,并返回删除元素的值;如果数组为空则返回undefined
unshift():将参数添加到原数组开头,并返回数组的长度
forEach():对数组进行遍历循环,对数组中的每一项运行给定函数。这个方法没有返回值,参数都是function类型,默认有传参,参数分别为:遍历的数组内容;对应的数组索引,数组本身
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的区别
var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
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作用域的一般理解
全局作用域
全局作用域在页面打开时被创建,在页面关闭时被销毁
编写在script标签中的变量和函数,作用域为全局,在页面的任何位置都可以访问到
在全局作用域中有全局对象window,代表一个浏览器窗口,可以直接调用
全局作用域中声明的变量和函数会作为window对象的属性和方法保存
全局变量:任何变量,未经声明而赋值,此变量为全局对象所有
a = 10 或 var a = b = 2
函数作用域
调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁
每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
在函数作用域中可以访问到全局作用域的变量,在函数之外无法访问到函数作用域内的变量
在函数作用域中访问变量、函数时,会先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域
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"
调用的两种方式:
整个脚本文件,在文件的第一行写入 "use strict",整个文件以严格模式运行
针对单个函数,将"use strict"放在函数体的第一行,则整个函数以"严格模式"运行
严格模式的限制:
不允许使用未声明的变量
禁止使用width语句
禁止this关键字指向全局对象
禁止在函数内部遍历调用栈
函数不能有重复的参数
对象不能有重名的属性
不允许对arguments赋值
9. JS事件的委托机制
JS事件的委托机制是基于JS的事件冒泡,给父元素绑定事件,再通过事件冒泡传递到子元素上
目的:为了减少对DOM的操作
优点:
绑定同类子元素时,不需要循环绑定事件
动态添加了相同类型的子元素,如果采用事件委托机制,新元素也会被相同的事件监听到,而采用传统的事件绑定,则新元素上并没有添加相同事件的监听。
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 也不会返回结果了