面试题-js基础
判断 js 类型的方式
1. typeof
可以判断出'string','number','boolean','undefined','symbol'
但判断 typeof(null) 时值为 'object'; 判断数组和对象时值均为 'object'
2. instanceof
原理是 构造函数的 prototype 属性是否出现在对象的原型链中的任何位置
function A() {}
let a = new A();
a instanceof A //true,因为 Object.getPrototypeOf(a) === A.prototype;
3. Object.prototype.toString.call()
常用于判断浏览器内置对象,对于所有基本的数据类型都能进行判断,即使是 null 和 undefined
4. Array.isArray()
用于判断是否为数组
map与forEach区别
参数相同
currentValue | 必须。当前元素的值 |
index | 可选。当前元素的索引值 |
arr | 可选。当前元素属于的数组对象 |
作用相同:遍历数组
forEach:调用数组的每个元素,并将元素传递给回调函数。
注意: forEach() 对于空数组是不会执行回调函数的。允许callback更改原始数组的元素,forEach()不会返回数据。
forEach不支持 continue,用 return false 或 return true 代替
forEach不支持 break,用 try catch/every/some 代替
map:返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
注意: map() 按照原始数组元素顺序依次处理元素,不会对空数组进行检测,不会改变原始数组。
addEventListener()第三个参数
1 这个参数设计到事件的捕获与冒泡,为true时捕获,false时冒泡。
捕获:从外面往里面触发事件:将每个监听事件的第三个参数设置为false,也就是默认的值。一般用的冒泡比较多
冒泡:从里面往外面触发事件:将每个监听事件的第三个参数设置为true。
2 事件流
页面中点击一个元素,事件是从这个元素的祖先元素中逐层传递下来的,这个阶段为事件的捕获阶段。
当事件传递到这个元素之后,又会把事件逐成传递回去,直到根元素为止,这个阶段是事件的冒泡阶段。
3 e.currentTarget e.target
var ul = document.querySelector('ul')
ul.addEventListener('click', function(e) {
e = e || window.event; // IE兼容
console.log(this); // this返回绑定事假的对象--ul
console.log(e.currentTarget)// 返回绑定事假的对象--ul
console.log(e.target) // 返回触发事件的对象 ---li
})
数组去重
1.ES6 的 Set
let arr = [1,1,2,3,4,5,5,6]
let arr2 = [...new Set(arr)]
let const var区别
1 var是ES5提出的;let和const是ES6提出的。
2 let和var声明的是变量,声明之后可以更改,声明时可以不赋值;const声明的是常量,必须赋值;
const 一旦声明必须赋值,不能使用null占位。声明后不能再修改,如果声明的是复合类型数据 ,数组,对象,可以修改其属性
3 var允许重复声明变量,后一个变量会覆盖前一个变量;let和const在同一作用域不允许重复声明变量,会报错。
4 var声明的变量存在变量提升(将变量提升到当前作用域的顶)。即变量可以在声明之前调用,值为undefined。
let和const不存在变量提升。即它们所声明的变量一定要在声明后使用,否则报ReferenceError错。
5 var不存在块级作用域。let和const存在块级作用域。
常用es6功能
模板字符串 `` 和模板变量 ${ } :
解构赋值
const {a,c} = obj
const [x,y,z] = arr
箭头函数
for-of(用来遍历数据—例如数组中的值。)
ES6 将 Promise 对象纳入规范,提供了原生的 Promise 对象。
增加了 let 和 const 命令,用来声明变量。
引入 module 模块的概念 模块化 类 export class
== 和 ===的区别
==是非严格意义上的相等 值相等就相等
===是严格意义上的相等,会比较两边的数据类型和值大小 值和引用地址都相等才相等
箭头函数与普通函数的区别?
箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值,,,任何方法都改变不了其指向,如 call(),apply() bind()
箭头函数没有原型属性
不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误
不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替
不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数
变量的生命周期
当一个变量的生命周期结束之后,它所指向的内存就会被释放。
js有两种变量,局部变量和全局变量,局部变量是在他当前的函数中产生作用,
当该函数结束之后,该变量内存会被释放,全局变量的话会一直存在,直到浏览器关闭为止。
attribute 和 property 的区别是什么?
对属性Property可以赋任何类型的值,而对特性Attribute只能赋值字符串!
attribute 是 dom 元素在文档中作为 html 标签拥有的属性
property 就是 dom 元素在 js 中作为对象拥有的属性。
property能够从attribute中得到同步;attribute不会同步property上的值
更改property和attribute上的任意值,都会将更新反映到HTML页面中
数组(array)方法
map : 遍历数组,返回回调返回值组成的新数组
forEach : 无法 break ,可以用 try/catch 中 throw new Error 来停止
filter : 过滤
some : 有一项返回 true ,则整体为 true
every : 有一项返回 false ,则整体为 false
join : 通过指定连接符生成字符串
push / pop : 末尾推入和弹出,改变原数组, 返回推入/弹出项
unshift / shift : 头部推入和弹出,改变原数组,返回操作项
sort(fn) / reverse : 排序与反转,改变原数组
concat : 连接数组,不影响原数组, 浅拷贝
slice(start, end) : 返回截断后的新数组,不改变原数组
splice(start,number,value…): 返回删除元素组成的数组,value 为插入项,改变原数组
indexOf / lastIndexOf(value, fromIndex) : 查找数组项,返回对应的下标
reduce / reduceRight(fn(prev, cur) ,defaultPrev) : 两两执行,prev 为上次化简函数的return 值,cur 为当前值(从第二项开始)
基本类型与引用类型
基本数据类型
Number、String、Boolean、Null、Undefined、Symbol、bigInt
引用数据类型
object、Array、Date、Function、RegExp
基本类型:就是值类型,在栈中申请空间,变量所对应的内存区域存储的是值
引用类型:就是地址类型,先在堆中申请空间存放数据,再把堆区的地址赋给arr,变量所对应的内存区域存储的是地址
浅拷贝与深拷贝
赋值:一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据
var arr1 = new Array(1,3,4)
Var arr2 = arr1;// 这就是一个最简单的赋值 ,arr1与arr2的在内存中存的是同样的地址
深拷贝和浅拷贝主要是针对引用类型(数组和对象)
基本类型赋值时,赋的是数据(所以,不存在深拷贝和浅拷贝的问题)
引用类型赋值时,赋的值地址(就是引用类型变量在内存中保存的内容)
浅拷贝:会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
如果属性是基本类型,拷贝的就是基本类型的值;
如果属性是内存地址(引用类型),拷贝的就是内存地址
只拷贝了一层,如果有更深一层,还是拷贝了地址
方法1 Object.assign()
var obj = { a: {xx: "啊啊啊"},b: 1 };
var newObj = Object.assign({}, obj);
newObj.a.xx = "不不不";
console.log(obj.a.xx); // 不不不
方法2 concat()
let arr = [1, 3, {
username: 'xx'
}];
let arr2=arr.concat();
方法3 slice()
let arr = [1, 3, {
username: ' kobe'
}];
let arr3 = arr.slice();
深拷贝:会创建一个新对象,复制了所有层级
方法1: 递归复制
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if(obj && typeof obj === "object") {
for(key in obj) {
if(obj.hasOwnProperty(key)) {
// 判断 obj 是否是对象,如果是,递归复制
if(obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone(obj[key]);
}else{
// 如果不是
objClone[key] = obj[key];
}
}
}
}
return objClone
}
方法2:JSON对象的parse和stringify
JSON.parse(JSON.stringify)深拷贝的问题
JSON.parse(JSON.stringfy(X)),其中X只能是Number, String, Boolean, Array, 扁平对象,即那些能够被 JSON 直接表示的数据结构。
function deepClone(obj) {
const objStr = JSON.stringify(obj);
const target = JSON.parse(objStr);
return target;
}
方法3 JQ的extend方法
$.extend( [deep ], target, object1 [, objectN ] )
deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。
防抖和节流
防抖
1.函数防抖 : 单位时间内,频繁触发事件,只会触发最后一次
2.经典场景 :
(1)输入框实时输入 oninput
(2)减少触发输入的频率,提高代码性能
3.防抖流程 :
(1)声明一个全局timeID存储定时器id
(2)每一次触发事件,先清除上一次定时器,以本次触发为准
(3)开启本次定时器
节流
1.函数节流 : 单位时间内,频繁触发事件,只会触发一次
2.应用场景 : 降低高频事件触发频率
(1)鼠标移动: onmousemove
(2)滚动条事件: onscroll
3.节流流程
(1)声明一个全局变量记录 本次触发时间
(2)每一次触发事件的时候, 获取当前时间
(3)判断 当前时间 - 上一次触发事件 >= 节流间隔
(4)存储本次触发事件,用于下一次判断
防抖:指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
思路:每次触发事件时都取消之前的延时调用方法:
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
节流:指连续触发事件,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。
思路:每次触发事件时都判断当前是否有等待执行的延时函数。
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
canRun = false; // 立即设置为 false
setTimeout(() => { // 将外部传入的函数的执行放在 setTimeout 中
fn.apply(this, arguments);
// 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));