盒子水平居中
flex布局
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
.father {
height: 300px;
display: flex;
justify-content: center;
align-items: center;
background: rebeccapurple;
}
.box {
height: 200px;
width: 300px;
text-align: center;
line-height: 200px;
background: #4078c0;
}
</style>
<body>
<div class="father">
<div class="box">盒子水平居中</div>
</div>
</body>
</html>
定位
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
.father {
height: 300px;
position: relative;
background: rebeccapurple;
}
.box {
position: absolute;
top: 50%;
left: 50%;
height: 200px;
width: 300px;
transform: translate(-50%, -50%);
text-align: center;
line-height: 200px;
background: #4078c0;
}
或者如下,但是这种都为0的写法要求盒子本身必需有宽高,不然不行就会沾满整个宽高
.box {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 200px;
width: 300px;
margin: auto;
text-align: center;
line-height: 200px;
background: #4078c0;
}
</style>
<body>
<div class="father">
<div class="box">盒子水平居中</div>
</div>
</body>
</html>
使用display: table-cell
组合使用display:table-cell和vertical-align: middle、text-align: center,使父元素内的所有行内元素水平垂直居中(内部div设置display:inline-block即可)。这在子元素不确定宽高和数量时,特别实用!但是父元素本身需要给定固定宽和高,百分比不是固定宽高
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
.father {
display: table-cell;
vertical-align: middle;
text-align: center;
height: 300px;
width: 880px;
background: rebeccapurple;
}
.box {
display: inline-block;
height: 200px;
width: 300px;
text-align: center;
line-height: 200px;
background: #4078c0;
}
</style>
<body>
<div class="father">
<div class="box">盒子水平居中</div>
</div>
</body>
</html>
通过js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
.father {
height: 300px;
width: 600px;
position: relative;
background: rebeccapurple;
}
.box {
height: 200px;
width: 300px;
text-align: center;
line-height: 200px;
background: #4078c0;
}
</style>
<body>
<div class="father">
<div class="box">盒子水平居中</div>
</div>
<script>
let father = document.querySelector('.father')
let box = document.querySelector('.box')
let fatherHeight = father.offsetHeight
fatherWidth = father.offsetWidth
boxHeight = box.offsetHeight
boxWidth = box.offsetWidth
box.style.position = 'absolute'
box.style.left = (fatherWidth - boxWidth) / 2 + 'px'
box.style.top = (fatherHeight - boxHeight) / 2 + 'px'
</script>
</body>
</html>
Dom事件处理函数中this的指向
<body>
// 如果直接写在标签里面,相当于告诉浏览器全局去找到这个click函数。所以this指向btnClick这个实例
<button class="btn" onclick="btnClick.click() ">点击</button>
</body>
<script>
function Button() {
this.clicked = false
this.click = function () {
this.clicked = true
console.log(this)
}
}
let btnClick = new Button()
let btn = document.querySelector('.btn')
btn.addEventListener('click', btnClick.click)// 作为回调函数调用,this指向该DOM对象
let button = {
clicked: false,
click: () => {
this.clicked = true
console.log(this)
},
}
btn.addEventListener('click', button.click) // 作为回调函数调用,箭头函数由于不会创建自己的this,它只能从作用域链的上层继承this,所以this指向全局对象window
</script>
手动实现call,apply,bind函数
call
Function.prototype.myCall = function (context, ...args) {
// 判断是否是undefined和null
/*
if (typeof context === 'undefined' || context === null) {
context = window
}
*/
context = context || window
let fn = Symbol()
context[fn] = this // 给context添加一个fn函数,谁调用myCall,this就指向谁,所以fn就是要执行的函数,
let result = context[fn](...args)
delete context[fn] // 因为context没有fn,所以还需要做一下删除
return result
}
// 测试
let name = 'Jerry'
let Person = {
name: 'Jack',
say: function () {
console.log('my name is', this.name)
console.log(arguments)
}
}
let Person1 = {
name: 'Tom'
}
Person.say.myCall(Person1, 1, 2, 3) // my name is Tom 1 2 3
Person.say.call(undefined, 1, 2, 3) // my name is Jerry 1 2 3
apply
Function.prototype.myApply = function (context, args) {
context = context || window
let fn = Symbol()
context[fn] = this
let result = context[fn](...args)
delete context[fn]
return result
// 测试
let name = 'Jerry'
let Person = {
name: 'Jack',
say: function () {
console.log('my name is', this.name)
console.log(...arguments)
}
}
let Person1 = {
name: 'Tom'
}
Person.say.myApply(Person1, [1, 2, 3]) // my name is Tom 1 2 3
Person.say.apply(undefined, [1, 2, 3]) // my name is Jerry 1 2 3
bind
Function.prototype.mybind = function (context, ...args1) {
if (typeof context === 'undefined' || context === null) {
context = window
}
let self = this // 函数本身
let bound = function (...args2) {
let allArgs = [...args1, ...args2]
return self.apply(context, allArgs)
}
// 同时还需要考虑到原型链的问题
// 为什么要考虑?因为在new 一个bind过生成的新函数的时候,必须的条件是要继承原函数的原型
// 寄生组合继承
let fn = function () {
}
fn.prototype = context.prototype
bound.prototype = new fn()
return bound
}
// 测试
function demo() {
console.log(this.name, [...arguments])
}
let obj = {
name: '测试bind函数'
}
demo.mybind(obj, 1, 2)(4, 5, 6) // 测试bind函数 [1, 2, 4, 5, 6]
demo.bind(obj, 1, 2)(4, 5, 6) // 测试bind函数 [1, 2, 4, 5, 6]
柯里化函数实现
使用场景
- 参数复用
function hobby(eat, drink, play, study) {
return `${eat}${drink}${play}${study}`
}
let res = hobby('吃','喝', '玩', '学习')
console.log(res) // 吃喝玩学习
// after curry
function hobby1(eat,drink) {
return function otherHobby(play, study) {
return `${eat}${drink}${play}${study}`
}
}
let res1 = hobby1('吃', '喝') // it's a function
console.log(res1('玩', '学习')) // 吃喝玩学习
console.log(res1('玩', '上班')) // 吃喝玩上班
const roleList1 = [
{male: '张三', skill: 'coding'},
{male: '赵四', skill: 'coding'}
]
const roleList2 = [
{female: 'aaa', skill: 'swimming'},
{female: 'bbb', skill: 'swimming'}
]
const curring = name => item => item[name]
const name1 = curring('male')
const name2 = curring('female')
console.log(roleList1.map(name1)) // ["张三", "赵四"]
console.log(roleList2.map(name2)) // ["aaa", "bbb"]
- 提前确认,避免每次都重复判断
let addEvent = function (el, type, fn, capture) {
if (window.addEventListener) {
el.addEventListener(type, function (e) {
fn.call(el, e)
}, capture)
} else if (window.attachEvent) {
el.attachEvent('on' + type, function (e) {
fn.call(el, e)
})
}
}
// 上面代码,每次使用addEvent为元素添加事件的时候,IE6,7,8都会走一遍if...else if...但是只要一次判定就可以了,使用函数柯里化优化
let addEvent = (function () {
if (window.addEventListener) {
return function (el, type, fn, capture) {
el.addEventListener(type, function (e) {
fn.call(el, e)
}, capture)
}
} else if (window.attachEvent) {
return function (el, type, fn, capture) {
el.attachEvent('on' + type, function (e) {
fn.call(el, e)
})
}
}
})()
- 延迟计算运行
function hobby(eat, drink, play, study) {
return `${eat}${drink}${play}${study}`
}
let res = hobby('吃','喝', '玩', '学习')
console.log(res) // 吃喝玩学习
// after curry
function hobby1(eat,drink) {
return function otherHobby(play, study) {
return `${eat}${drink}${play}${study}`
}
}
let res1 = hobby1('吃', '喝') // it's a function //这里返回的是一个函数,达到了延迟计算运行的目的
curry封装
function curry(fn) {
let len = fn.length
return function temp() {
let args = [...arguments]
if (args.length >= len) {
return fn(...args)
} else {
return function () {
return temp(...args, ...arguments)
}
}
}
}
// 测试
function print(a, b, c) {
console.log(a + b + c)
}
let res = curry(print) // 延迟计算运行,没有足够的参数时,不会返回结果
res(1, 2)(3) // 6
// 可以curry的同时进行传参
function curry(fn, ...args) {
if (args.length < fn.length) {
return (...arguments) => curry(fn, ...args, ...arguments)
} else {
return fn(...args)
}
}
function print(a, b, c) {
console.log(a + b + c)
}
let res = curry(print,2)
res(1)(2) // 5
防抖函数
防抖函数的作用
防抖函数的作用就是控制函数在一定时间内的执行次数。防抖意味着N秒内函数只会被执行一次,如果N秒内再次被触发,则重新计算延迟时间。
举例说明: 小思最近在减肥,但是她非常吃吃零食。为此,与其男朋友约定好,如果10天不吃零食,就可以购买一个包(不要问为什么是包,因为包治百病)。但是如果中间吃了一次零食,那么就要重新计算时间,直到小思坚持10天没有吃零食,才能购买一个包。所以,管不住嘴的小思,没有机会买包(悲伤的故事)... 这就是 防抖
function debounce(func, delay) {
let timer
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
function demo() {
console.log('123')
}
setInterval(debounce(demo,500),1000) // 第一次在1500ms后触发,之后每1000ms触发
setInterval(debounce(demo, 2000),1000) // 永远不会执行,因为它每过一秒钟就执行一次,而debounce要delay两秒钟
防抖的应用场景
- 搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。
- 表单验证
- 按钮提交事件。
- 浏览器窗口缩放,resize事件(如窗口停止改变大小之后重新计算布局)等。
节流函数
节流函数的作用是规定一个单位时间,在这个单位时间内最多只能触发一次函数执行,如果这个单位时间内多次触发函数,只能有一次生效。
function throttle(fn, gapTime) {
let _lastTime = null;
return function () {
let _nowTime = Date.now()
if (_nowTime - _lastTime > gapTime || !_lastTime) {
fn();
_lastTime = _nowTime
}
}
}
let fn = ()=>{
console.log('boom')
}
setInterval(throttle(fn,1000),10)
节流的应用场景
- 按钮点击事件
- 拖拽事件
- onScoll
- 计算鼠标移动的距离(mousemove)
请实现一个 flattenDeep 函数,把嵌套的数组扁平化
这里提供三种方法,分别如下:
- 调用ES6中Array的新原型方法flat
// flat方法默认是压平一层
// 当传递的整数大于数组嵌套的层数时,会将数组拉平为一维数组,JS能表示的最大数字为 Math.pow(2, 53) - 1,因此我们可以这样定义 flattenDeep 函数
function flattenDeep(arr) {
// 正常情况下我们都不需要这么大的层级嵌套
return arr.flat(Math.pow(2, 53) - 1)
}
console.log(flattenDeep([1, [2, [3, [4]], 5]])) // [1,2,3,4,5]
- 利用reduce和concat方法
关于reduce方法:回调函数第一次执行时,accumulator 和currentValue的取值有两种情况:如果调用reduce()时提供了initialValue,accumulator取值为initialValue,currentValue取数组中的第一个值;如果没有提供 initialValue,那么accumulator取数组中的第一个值,currentValue取数组中的第二个值`
function flattenDeep(arr) {
return arr.reduce((acc, curVal) => Array.isArray(curVal)
// 当currentVlaue是一个数组时,利用递归处理
? acc.concat(flattenDeep(curVal))
// initialValue设置为一个空数组
: acc.concat(curVal), [])
}
console.log(flattenDeep([1, [2, [3, [4]], 5]])) // [1,2,3,4,5]
- 利用栈数据结构处理
function flattenDeep(arr) {
const stack = [...arr]
const res = []
while (stack.length) {
// 栈结构,使用pop从stack取出值
const next = stack.pop()
if (Array.isArray(next)) {
// 如果pop出来是数组,就把它展开拷贝一份在push进去
stack.push(...next)
} else {
res.push(next)
}
}
return res.reverse()
}
console.log(flattenDeep([1, [2, [3, [4]], 5]])) // [1,2,3,4,5]
数组去重
// 使用ES6的Set去重
function unique(arr) {
return Array.from(new Set(arr))
}
// 使用for循环嵌套for循环,然后利用splice去重
function unique(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
// 第一个等于第二个,使用splice方法删除第二个
if (arr[i] == arr[j]) {
arr.splice(j, 1)
// 因为删除了一个,所以数组中的后面的元素索引发生了改变,因此需要j--拿到删除之后新的索引
j--
}
}
}
return arr
}
// 使用indexOf去重
function unique(arr){
if (!Array.isArray(arr)) {
throw new Error('args should a array‘)
}
let array = []
for (let i = 0; i < arr.length; i++) {
if (array.indexOf(arr[i]) === -1) {
array.push(arr[i])
}
}
return array
}
// 使用includes
function unique(arr) {
if (!Array.isArray(arr)) {
throw new Error('args should a array')
}
let array = []
for (let i = 0; i < arr.length; i++) {
if (!array.includes(arr[i])) {
array.push(arr[i])
}
}
return array
}
let arr = [1, 1, 3, 6, 8, 8, 9]
let array = unique(arr)
console.log(array) [1,3,6,8,9]
实现一个深拷贝函数
function deepClone(target) {
// typeof判断是否是引用类型
if (typeof target !== 'object' || target == null) {
return target
}
// 如果是引用类型,在判断它是数组还是对象
let res = Array.isArray(target) ? [] : {}
for (const key in target) {
// hasOwnProperty方法确保key属性是在自身上,而不是原型上
if (target.hasOwnProperty(key)) {
res[key] = deepClone(target[key])
}
}
return res
}
let source = {
name: 'Tom',
age: 19,
address: {
city: 'HangZhou',
population: '30 million',
},
}
let res = deepClone(source)
source.address.city = 'ShangHai'
console.log(res.address.city) // HangZhou
数组排序
1. 冒泡排序
let arr = [1, 5, 7, 8, 2, 89, 10]
function bubbleSort(arr) {
// 第一层循环就是决定全部排好要循环多少次
for (let i = 0; i < arr.length - 1; i++) {
// 第二层循环对元素大小进行比较,随着不断的比较,范围也要不断的缩小,所以要减i
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
let tmp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = tmp
}
}
}
return arr
}
2. 选择排序
let arr = [1, 5, 7, 8, 2, 89, 10]
function selectSort(arr) {
// 第一层循环就是决定全部排好要循环多少次
for (let i = 0; i < arr.length - 1; i++) {
let minIndex = i
for (let j = i + 1; j < arr.length; j++) {
// 找出最小的值,将他们的索引进行替换
if (arr[j] < arr[minIndex]) {
minIndex = j
}
}
// 声明一个临时变量将他们的值进行替换
let temp = arr[i]
arr[i] = arr[minIndex]
arr[minIndex] = temp
}
return arr
}
console.log(selectSort(arr))
3. 归并排序(这种排序的复杂度为o(nlogn),场景中用的比较多)
function mergeSort(arr) {
// 如果数组中只有一个元素,就return这个arr
if (arr.length === 1) {
return arr
}
let middle = Math.floor(arr.length / 2),
left = arr.slice(0, middle),
right = arr.slice(middle)
// mergeSort递归处理
return merge(mergeSort(left), mergeSort(right))
}
function merge(left, right) {
let result = []
while (left.length && right.length) {
if(left[0] <= right[0]) {
result.push(left.shift())
} else {
result.push(right.shift())
}
}
while (left.length)
result.push(left.shift())
while (right.length)
result.push(right.shift())
return result
}
洗牌算法
function shuffle(arr) {
const copyedArr = [...arr]
const len = copyedArr.length
for (let i = 0; i < len; i++) {
const randomIndex = random(i, len-1);
[copyedArr[i], copyedArr[randomIndex]] = [copyedArr[randomIndex], copyedArr[i]]
}
return copyedArr
}
function random(n, m) {
return Math.floor(Math.random()*(m-n+1)) + 1
}
let arr = [5, 7, 89, 3, 7, 8, 2, 9, 0]
console.log(shuffle(arr))