1.数据类型
数组、函数的本质其实是Object。
(1)五种基本数据类型 + 一种引用数据类型
五种基本数据类型: number、string、boolean、undefined、null。
一种引用数据类型: object
(2)数据类型检测
方法1: typeof 变量
方法2: 变量 instanceof Array
(3)数据类型转换(核心)
// 隐式转换
(1)转number: let n = +a
(2)转string: let n = a + ''
(3)转Boolean:let n = !a // 转Boolean时,只有false、null、undefined、0、NaN、''的情况下为false。
(4)短路运算: 最终返回的结果是判定后的结果,而不是boolean值。
// 强制转换
(4)包装类
JS为boolean、string、number单独创建了一个构造函数,将它们包装成一个对象。
2.基础知识
01.事件循环、事件流
02.事件绑定
方法1:使用addEventListener
// 1.绑定事件:默认是冒泡方式。
dom.addEventListener('事件类型','事件处理程序',[事件冒泡方式], { once: true })
// 2.删除事件
dom.removeEventListener('事件',需要取消绑定的函数名称) // 函数不需要加小括号
方法2:传统方式
// 1.绑定事件
对象.onclick = function(){} // 缺点:只能绑定一个事件。
// 2.删除事件
标签.事件 = null
03.事件大全、事件对象
随时可以查询。
1.事件大全
// 1. 鼠标事件
(1)click: 鼠标左键。
(2)mouseover: 鼠标经过。(有冒泡)
mouseenter: 鼠标经过。(没有冒泡,推荐!)
(3)mouseout: 鼠标离开。(有冒泡)
mouseleave: 鼠标离开。(没有冒泡,推荐!)// mouserover和mouseenter的区别:mouseover经过自身会触发,经过子元素还会触发。
(4)mousemove: 鼠标移动。
(5)mouseup: 鼠标按下后,然后弹起时触发。
(6)mousedown: 鼠标按下触发。
(7)contextmenu: 禁止鼠标右键菜单(给document绑定,触发时回调函数写:e.preventDefault())
(8)selectstart: 禁止鼠标选中文字(给document绑定,触发时回调函数写:e.preventDefault())
// 2. 键盘事件
(1)keyup:按键松开时触发 // keydown和keypress键盘按下触发,但是文字还没落入到文本框就触发了,但是keyup是恰恰相反。
(2)keydown: 按键按下时触发(识别功能键)
(3)keypress:按键按下时触发(不识别功能键,如Ctrl,Shift等) // keyup事件不区分大小写,keypress事件可以区分大小写。
// 3. 触摸事件(移动端)
(1)touchstart:手指摸到dom时触发
(2)touchmove: 手指在dom上滑动时触发
(3)touchend: 手指从dom中离开时触发
// 4. 视频播放位置改变事件
(1)ontimeupdate:当视频、音频的当前播放位置改变时触发。(需要配合节流)
(2)onloadeddata:在当前帧的数据加载完成,并且还没有足够的数据播放音频、视频的下一帧时触发。
(3)获取音频、视频当前播放的时间:video.currentTime
// 5. 表单获取焦点事件
(1)focus:
(2)blur:
(3)change:当表单内容发生改变时才触发。
// 6. 用户输入事件
(1)input:
// 7. 页面加载事件
(1)load:等待页面所有资源都加载完,才会触发。 // 给资源对象绑定,如:window、img等。
(2)DOMContentLoaded:当HTML解析完结构就触发,无需等待css样式,图片等加载完。// 给document对象绑定
// 8. 页面尺寸改变事件
(1) resize:
// 9. 页面滚动事件
/* 需要关注属性scrollTop、scrollLeft,这个属性是HTML标签的属性哦, 比如div.scrollTop, html的scrollTop */
(1)scroll:页面滚动时触发。 // 给window对象绑定(其实给某一个元素绑定也行)
2.事件对象
// 1. 公共事件对象的属性和方法
(1)e.target: 返回触发事件的对象。(标准)
(2)e.srcElement: 返回触发事件的对象。(非标准,IE6-8使用)
(3)e.type: 返回事件类型,如click。
(4)e.returnValue: 阻止默认行为,比如让链接不跳转(非标准IE6-8使用)
(5)e.preventDefault(): 阻止默认行为(标准)
(7)e.stoppPropagation() 阻止事件冒泡(标准)
// 2. 鼠标事件对象的属性
(1)e.clientX: 返回鼠标相对浏览器窗口可视区的x坐标。
(2)e.clientX: 返回鼠标相对浏览器窗口可视区的y坐标。
(3)e.pageX: 返回鼠标相对文档页面的x坐标。
(4)e.pageY: 返回鼠标相对文档页面的y坐标。
// 3. 键盘事件对象的属性
(1)e.keycode(已淘汰!)
(2)e.key(获取用户按了哪个键)
// 4. 手指触摸事件对象的属性 (前三个获取集合,集合里面放着下面的属性)
(1)e.touches 返回正在触摸屏幕的所有手指的集合。(屏幕被多少个手指触摸)
(2)e.targetTouches 返回正在触摸当前DOM元素上的手指的集合。(DOM元素被多少个手指触摸)
(3)e.changedTouches 手指状态发生改变的集合,从有到无,从无到有。
(1)e.targetTouches[0].pageX: 返回手指相对文档页面的x坐标。
(2)e.targetTouches[0].pageY: 返回手指相对文档页面的y坐标。
04.原型链
(1)必须疏通一个理念:函数也是一个对象。
// 函数也是对象
(1) 构造函数既是一个函数,也是一个对象,Function是c++提供的函数,用于创建构造函数用的。(Function理解为创世神一般的存在)
// 只要是个对象,就有__proto__属性
(2) 每个对象都有一个原型对象,通过该'对象.__proto__'可以访问的到原型对象。
// 只要是个函数,就有prototype属性
(3) 每个函数有一个原型对象,通过'函数名.prototype'。(原型对象: 就是创造者的原型对象)
(2)原型链关系图(必须吃透!)
(3)原型的使用场景
// 1.如果想让所有对象,都拥有同一个属性或方法,可以这样做。
Object.prototype.myMethod = function() {
console.log("该方法让所有调用者共享")
}
************************************************************************************
// 2.如果想让所有的函数,同时拥有一个方法,可以这样做:
Function.prototype.bind = function() {
console.log("这就是为什么函数能调用bind方法")
}
************************************************************************************
// 3.创建一个干净的对象,即:一个没有原型的对象。(调用Object.create(原型对象, [属性组成的对象]))
const obj = Object.create(null)
const user = {
name: '思密达',
age: 15
}
Object.serPrototyOf(user, obj) // 含义:把对象user的原型对象设置为obj。
************************************************************************************
// 4.一次性往原型上添加多个属性/方法
Star.prototype = {
constructor: Star, // 看过来,需要将原型对象的constructor属性加回去,才能保证原型链不断开。
sing: function(){},
dance: function(){}
}
(4)原型的面试题
__proto__指向的是:该对象创造者的原型对象。
05.计时器
(1)两种类型的计时器
// 倒计时定时器
(1) setTimeout(回调函数, 延迟毫秒数)
(2) clearTimeout(定时器名称)
// 循环计时器
(1) setInterval(回调函数, 间隔时间毫秒数)
(2) clearInterval(定时器名称)
(2)定时器的使用细节
// 开启定时器
function start() {
if(timerId) return
let timerId = setTimeout(function() {
...
}, 3000)
}
// 销毁定时器
function stop() {
clearTimeout(timerId)
timerId = null // 不仅仅要clearTimeout(),还需要将原来的timerId设置为null哦。
}
06.本地存储
(1)SessionStorage
//(1)生命周期
页面共享: 只能同一个标签页才能访问,不同标签页不能访问。
生命周期: 当关闭浏览器窗口后,sessionStorage对象销毁。
//(2)方法(如果存储对象,需要转为JSON格式)
(1) 存储数据: sessionStorage.setItem(key,value)
(2) 获取数据: sessionStorage.getItem(key)
(3) 删除一个数据: sessionStorage.removeItem(key)
删除全部数据:sessionStorage.clear()
(2)LocalStorage
//(1)生命周期
页面共享: 存储在浏览器中,可以实现多页面共享。(但是不能跨域,跨域的时候无法获取数据)
生命周期: 除非手动删除,否则数据一直存在。
//(2)方法(如果存储对象,需要转为JSON格式)
(1) 存储数据: LocalStorage.setItem(key,value)
(2) 获取数据: LocalStorage.getItem(key)
(3) 删除一个数据: LocalStorage.removeItem(key)
删除全部数据:LocalStorage.clear()
07.Promise
08.动画
09.渲染帧
(1)问题描述:使用setInterval()会存在渲染帧率浮动的问题
(2)解决方案:使用H5新增的API:requestAnimationFrame()
10.ES6
1.展开运算符
妙用:把伪数组 => 真数组
// 示例1:
let arr = [1, 2, 3, 4]
console.log(...arr)
// 示例2:
let ary1 = [1, 2, 3, 4]
let ary2 = [7, 8, 9, 10];
let ary3 = [...ary1, ...ary2]; // 输出结果是:[1,2,3,4,7,8,9,10]
// 示例3:
let lis = document.querySelector('li');
let arr = [...lis];
2.解构
(1)数组解构
// 准备数据:
let arr = [1, 2, 3, '草莓', ['小米','华为'] ];
// 方法1:直接解构赋值
let [s1, ...s2] = arr; // 此时s1的值为1,...s2的值为[2,3]
let [a, b, c, d, [e, f] ] = array;
// 方法2:间隔取值,比如说我只想取到草莓的值。
const [, , , a] = arr
(2)对象解构
// 准备数据
let person = {
name: "张三",
age: 18,
address: {
province: '广东',
city: '深圳'
}
};
// 方法1:解构后的变量用原名。
let { name, age, address: {province, city} } = person;
let { address: { province, city }} = person;
// 方法2:解构后的变量用别名。
const { name: username, age: myAge } = person
// 解构时使用默认值,如果person中不存在nickname属性,也不会导致解构出来是undefined。
const { name, age, nickname='思密达' } = person
(3)对象数组解构
// 准备数据
let pig = [
{
uname: '佩奇',
age: 18
},
{
uname: '小猪',
age: 28
}
];
// 解构赋值
let [{ uname:peiqi, age:page },{ uname:xiaozhu, age:xage }] = pig;
3.剩余参数
其实就是相对于Java中的可变形参,当数组看即可。
function fun(a, b, ...arr) {
console.log(arr)
}
fun(1,2); // 输出结果:[]
fun(1,2,3,4,5); // 输出结果:[3,4,5]
4.this指向问题
(1)确定this指向的是谁
// (1) 普通函数: 谁调用this就指向谁。(运行时才知道)
// (2) 箭头函数: 这个this,无需知道谁调用函数,找到箭头函数的上一级作用域的this,就是箭头函数的this。(写代码时就能确定了)
function debounce() {
return () => {
console.log(this); // 箭头函数的this = debounce函数的this,debounce函数被window调用,所以this指向window对象。
}
}
debounce()()
(2)修改this指向(三种方法)
function add(a, b) {
console.log(a, b);
console.log(this);
}
// 方法1: 修改this的同时,还会调用函数。 call(this指向的对象, 实参1, 实参2...) (了解!)
add.call(obj, 3, 4)
// 方法2: 修改this的同时,还会调用函数。 apply(this指向的对象, [实参1, 实参2...])
add.apply(obj, [3, 4])
// 方法3: 只会修改this指向,但是不会调用函数。 bind(this指向的对象, 实参1, 实参2...) (掌握!)
add.bind(obj, 3, 4)()
// 使用案例
var btn = document.querySelector('button');
btn.onclick = function() {
this.disabled = true;
setTimeout(function(){
this.disabled = false;
}.bind(btn),3000);
}
5.属性描述符
(1)什么是属性描述符
// 源码
const user = {
name: 'monica',
age: 17
}
// 原理
const user = {
// 属性 name 的描述符
name: {
value: 'monica',
configurable: true, // 该属性的描述符是否可以被重新定义
enumerable: true, // 该属性是否允许被遍历,会影响for-in循环
writable: true // 该属性是否允许被修改
},
// 属性 age 的描述符
age: {
value: 'monica',
configurable: true, // 该属性的描述符是否可以被重新定义
enumerable: true, // 该属性是否允许被遍历,会影响for-in循环
writable: true // 该属性是否允许被修改
}
}
(2)获取对象的一个属性描述符:Object.getOwnPropertyDescriptor(obj, propertyName)
const user = {
name: 'monica',
age: 17
}
Object.getOwnPropertyDescriptor(user, 'name');
/*
{
value: 'monica',
configurable: true, // 该属性的描述符是否可以被重新定义
enumerable: true, // 该属性是否允许被遍历,会影响for-in循环
writable: true // 该属性是否允许被修改
}
*/
(3)修改对象的一个属性描述符:Object.defineProperty(obj, propertyName, descriptor)
const user = {
name: 'monica',
age: 17
};
Object.defineProperty(obj, 'name', {
value: '邓哥', // 将其值进行修改
enumerable: false, // 让该属性不能被遍历
writable: false // 让该属性无法被重新赋值
})
6.getter、setter(vue数据劫持)
属性描述符中有两个特殊的配置,分别为
get
和set
,通过它们,可以把属性的取值和赋值变为方法调用。
const obj = {
a: 10
};
Object.defineProperty(obj, 'a', {
get() { // 读取属性a时,得到的是该方法的返回值
return 1;
},
set(val) { // 设置属性a时,会把值传入val,调用该方法
console.log(val)
}
})
console.log(obj.a); // 输出:1
obj.a = 3; // 输出:3
console.log(obj.a); // 输出:1
7.参数默认值
// 对参数 b 使用了默认值1, 对参数 c 使用默认值2
const method = (a, b = 1, c = 2, d) => {
console.log(a, b, c, d)
}
method(1, 2); // 1 2 2 undefined
method(1); // 1 1 2 undefined
method(1, undefined, undefined, 4); // 1 1 2 4
11.BOM、DOM操作
待更新。。。
12.回流、重绘
回流:就是重新计算DOM节点的大小、位置。(耗费性能)
重绘:就是重新绘界面。
(1)回流
// 1.引起回流的情况
(1)修改DOM的几何信息的大小、位置。(例如:width、height、margin、padding)
(2)读取DOM的几何信息的大小、位置。(例如:clientWidth、clientHeight)
// 2.浏览器对回流的优化:如果发生了连续回流,就只会回流一次。
dom.style.width = '100px'
dom.style.height = '200px'
dom.style.left = '100px'
dom.style.top = '100px'
// 3.强制回流
去看第一个引起回流的情况即可。
(2)重绘
// 1.什么情况会引起重绘
(1)修改背景颜色、字体颜色
(2)修改圆角边框
(3)修改背景图片
(3)回流和重绘哪个耗费性能?
回流耗费性能,它需要利用CPU进行计算。
重绘几乎不会耗费性能,它是利用GPU工作的。
(4)怎么解决回流问题?
使用transform来代替margin,但是transform本身其实是会导致回流的,真正的解决方案是:给消耗性能的盒子设置【transform:translateZ(0px)】,开启三维空间,此时该盒子就会被分配到另外一个图层,该图层完全由GPU进行管理,这就是所说的开启了GPU加速,此时才不会发生回流,但是一定会发生重绘。
13.async和defer的区别
相同点:async和defer都是异步加载的,也就是说JS会和HTML同时加载。
不同点:async的话,JS加载完马上执行,执行期间HTML会停止解析,等JS执行完毕。defer的话,当JS加载完后,不会马上执行,要等HTML解析完后,才会执行JS。
defer的好处:能解决首次加载出现白屏的问题。(因为它会等HTML执行完,才执行JS)
3.JS骚操作
01.防抖、节流
(1)防抖
// 防抖函数
function debounce(callback, duration) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => { callback.apply(this, args) }, duration)
}
}
// 防抖函数的使用示例
let box = document.querySelector('.box')
let debounce_instace = debounce(function () {
console.log(111);
}, 1000)
box.addEventListener('keyup', debounce_instace)
(2)节流
// 节流函数
function throttle(callback, duration) {
let flag = true;
return function(...args) {
if (flag) {
flag = false;
setTimeout(() => {
callback.apply(this,args)
flag = true
}, duration)
}
}
}
// 节流函数的使用示例
box.addEventListener('mousemove', throttle())function play() {
this.innerHTML = parseInt(this.innerHTML) + 1
console.log(this);
}
let div = this.document.querySelector('div')
div.addEventListener('mousemove', throttle(play, 3000))
02.对象克隆
深克隆(三种方法)
// 方法1: 使用Lodash库,调用"_.cloneDeep(传入要拷贝的对象)"方法,返回新的克隆对象。
// 方法2:把对象转为JSON字符串,然后再把JSON字符串转回对象。(很妙!)
// 方法3:递归实现深拷贝
function deepClone(oldObject) {
let newObject = null;
oldObject instanceof Array ? newObject = [] : newObject = {};
for (let key in oldObject) {
if (oldObject[key] instanceof Array) {
newObject[key] = deepClone(oldObject[key]);
} else if (oldObject[key] instanceof Object) {
newObject[key] = deepClone(oldObject[key]);
} else {
newObject[key] = oldObject[key]
}
}
return newObject;
}
使用示例:
// 准备数据
var data = {
id: 1,
name: '家电',
arr: [11, 22, 33, 44, { username: 'zhangsan', password: 123 } ],
goods: {
id: 11,
gname: '冰箱',
family: {
hobby: 1,
sex: '男'
}
}
}
// 调用深克隆函数
deepClone(data)
03.对象合并
方法1:循环判断
function mixObject(obj1, obj2) {
let newObj = {}
// 将obj2的键值对拷贝到newObj中
for(let prop in obj2) {
newObj[prop] = obj2[prop]
}
// 找obj1有的属性,但是obj2中没有的属性,放到newObj中
for(let prop in obj1) {
if(!(prop in obj2)) {
newObj[prop] = obj1[prop]
}
}
return newObject
}
方法2:Object.assign(gn(对象1, 对象2, 对象3, ...
)
// 功能:该方法会将后面的对象的属性合并到前面的对象,一直往前合并到最前面的对象,然后返回最前面的那个对象。
// 常用写法如下:这样就不怕修改最前面的那个对象本身了。
Object.assign({}, obj1, obj2)
方法3:ES6的扩展运算符
const obj = {...obj1, ...obj2}
应用场景:一个函数,需要传入对象options作为参数,如果参数不全,则需要用默认值来替代。
// (1)准备一个对象作为数据
const obj = {
name: '张益达',
dog: '小明'
}
// (2)该函数需要传入对象options作为参数,如果参数不全,则需要用默认值来替代,最终返回合并后的对象。
function complicate(options) {
const obj = {
name: '思密达',
age: 18,
home: 'China',
dog: '小妹'
}
return Object.assign({}, obj, options)
}
// (3)最终合并后的结果:{ name: '张益达', age: 18, home:'China', dog:'小明' }
complicate(obj)
04.树形结构 转 平铺结构
步骤1:准备数据1:平铺结构
// 准备数据
let oldArray = [
{
id: 1,
pid: 0,
name: '传智教育',
},
{
id: 2,
pid: 1,
name: '财务部',
},
{
id: 3,
pid: 1,
name: '教育部',
},
{
id: 4,
pid: 3,
name: '教育部长',
},
{
id: 5,
pid: 2,
name: '财务部长',
}
]
步骤1:准备数据2:树状数据
let oldArray = [
{
id: 1,
pid: 0,
name: '传智教育',
children: [
{
id: 2,
pid: 1,
name: '财务部',
children: [
{
id: 5,
pid: 2,
name: '财务部长',
}
]
},
{
id: 3,
pid: 1,
name: '教育部',
children: [
{
id: 4,
pid: 3,
name: '教育部长',
},
]
},
]
}
]
步骤2:转化函数:平铺转树状(核心)
/*
* @params {Array} list 要转化的数组。
* @params {DOM} rootValue 根节点,开头为0。
*/
function getTreeData(list, rootValue) {
const arr = []
list.forEach(item => {
if(item.pid === rootValue) {
arr.push(item)
const children = getTreeData(list, item.id)
item.children = children
}
})
return arr
}
步骤2:转换函数:树状转平铺(核心)
// 递归(树形结构 转 平铺结构)
function getArrayData(list) {
const arr = []
list.forEach(item => {
arr.push(item)
// 判断有没有children属性
if(item.children) {
const newArr = getArrayData(item.children)
arr.push(...newArr)
// 如果有些人item的chidren为空数组,那没必要存在这个属性了,删掉。
delete item.children
}
})
return arr
}