一、CSS布局
垂直居中
-
定位+margin+计算
//父盒子宽度的一半减去子盒子宽度的一半 500/2 - 200/2 = 150 //父盒子高度的一半减去子盒子高度的一半 500/2 - 200/2 = 150 .parent { position: relative; } .child { position: absolute; margin-top:150px; margin-left:150px; } -
定位+margin+负值
.child { top: 50%; left: 50%; margin-top:-50px; margin-left:-50px; } -
定位+margin+transform(不需要知道宽高)
.child { top: 50%; left: 50%; transform: translate(-50%, -50%); } -
定位+margin+auto(不需要知道宽高)
.child { top: 0px; left: 0px; bottom: 0px; right: 0px; margin: auto; } -
flex布局(不需要知道宽高)
//将父盒子设置成弹性盒容器 让子元素水平居中,垂直居中 .parent { display: flex; justify-content: center; align-items: center; } -
table布局(不需要知道宽高)
//设置父元素为display:table-cell,子元素设置 display: inline-block。 //利用vertical和text-align可以 让所有的行内块级元素水平垂直居中 .parent{ display: table-cell; vertical-align: middle; text-align: center; } .child{ display: inline-block; } -
grid网格布局(不需要知道宽高)(兼容性比较差)
.parent { display: grid; justify-content: center; align-items: center; } -
JavaScript
<body> <div class='parent' id='parent'> <div class='child' id='child'></div> </div> <script> let parent = document.getElementById('parent'); let child = document.getElementById('child'); let parentW = parent.offsetWidth; let parentH = parent.offsetHeight; let childW = child.offsetWidth; let childH = child.offsetHeight; parent.style.position = "relative" child.style.position = "absolute"; child.style.left = (parentW - childW) / 2 + 'px'; child.style.top = (parentH - childH) / 2 + 'px'; </script> </body>
两栏布局
- 一个定宽 一个自适应
- 左边栏使用 float 左浮
- 右边栏使用 margin-left 撑出内容块做内容展示
- 为父级元素添加BFC,防止下方元素飞到上方内容
<style>
.container{
overflow: hidden; 添加BFC
}
.left {
float: left;
width: 200px;
background-color: gray;
height: 400px;
}
.right {
margin-left: 210px;
background-color: lightgray;
height: 200px;
}
</style>
<div class="container">
<div class="left">左边</div>
<div class="right">右边</div>
</div>
- flex弹性布局
<style>
.container{
display: flex;
}
.left {
width: 100px;
}
.right {
flex: 1;
}
</style>
<div class="container">
<div class="left">左边</div>
<div class="right">右边</div>
</div>
三栏布局
实现三栏布局中间自适应的布局方式有:
两边使用 float,中间使用 margin(中间块放到最下面)
原理:
- 两边固定宽度,中间宽度自适应。
- 利用中间元素的margin值控制两边的间距
- 宽度小于左右部分宽度之和时,右侧部分会被挤下去
缺陷:
- 主体内容是最后加载的。
- 右边在主体内容之前,如果是响应式设计,不能简单的换行展示
<style>
.container{
width: 800px;
height: 800px;
background-color: #e9cfcf;
overflow: hidden;
}
.left{
width: 200px;
height: 200px;
background-color: #f88080;
float: left;
}
.right{
width: 200px;
height: 200px;
background-color: #cb2424;
float: right;
}
.middle{
height: 200px;
background-color: #41c49b;
margin-left: 200px;
margin-right: 200px;
}
</style>
<div class="container">
<div class="left">左侧</div>
<div class="right">右侧</div>
<div class="middle">中间</div>
</div>
两边使用 absolute,中间使用 margin
原理:
- 左右两边使用绝对定位,固定在两侧。
- 中间占满一行,但通过 margin和左右两边留出10px的间隔
<style>
.container{
width: 800px;
height: 800px;
background-color: #e9cfcf;
position: relative;
}
.left{
width: 200px;
height: 200px;
background-color: #f88080;
position: absolute;
top: 0;
left: 0;
}
.right{
width: 200px;
height: 200px;
background-color: #cb2424;
position: absolute;
top: 0;
right: 0;
}
.middle{
height: 200px;
background-color: #41c49b;
margin-left: 200px;
margin-right: 200px;
}
</style>
<div class="container">
<div class="left">左侧</div>
<div class="right">右侧</div>
<div class="middle">中间</div>
</div>
flex实现
<style>
.container{
width: 800px;
height: 800px;
background-color: #e9cfcf;
display: flex;
justify-content: space-between;
}
.left{
width: 200px;
height: 200px;
background-color: #f88080;
}
.right{
width: 200px;
height: 200px;
background-color: #cb2424;
}
.middle{
height: 200px;
background-color: #41c49b;
flex: 1;
}
</style>
<div class="container">
<div class="left">左侧</div>
<div class="right">右侧</div>
<div class="middle">中间</div>
</div>
双飞翼布局
两侧内容宽度固定,中间内容宽度自适应 三栏布局,中间一栏最先加载、渲染出来(主要内容) 实现方式 【浮动+margin负值】
- 左右开启浮动+清除浮动
- 主区域两侧留白【margin】
- 两侧上去【margin负值】
<style>
.container {
width: 100%;
background-color: skyblue;
float: left;
}
.center {
margin: 0 100px;
}
.left {
width: 100px;
background-color: green;
float: left;
margin-left: -100%; //!!!!!!!!!!
}
.right {
width: 100px;
background-color: red;
float: left;
margin-left: -100px; //!!!!!!!!!!
}
</style>
<body>
<div class="container ">
<div class="center">主区域</div>
</div>
<div class="left ">左区域</div>
<div class="right ">右区域</div>
</body>
圣杯布局
两侧内容宽度固定,中间内容宽度自适应 三栏布局,中间一栏最先加载、渲染出来(主要内容) 实现方式 【浮动+margin负值+定位】
- 开启浮动+清除浮动
- 主区域两侧留白 【padding】
- 两侧上去【margin负值+相对定位】
<style>
.container{
padding: 0 100px;
}
.center {
width: 100%;
background-color: #bc6b6b;
float: left;
}
.left {
width: 100px;
background-color: green;
float: left;
margin-left: -100%;
position: relative;
left: -100px;
}
.right {
width: 100px;
background-color: red;
float: left;
margin-right: -100px;
}
</style>
<body>
<div class="container">
<div class="center">主区域</div>
<div class="left ">左区域</div>
<div class="right ">右区域</div>
</div>
</body>
三角形
<body>
<div>
</div>
</body>
<style>
div {
width: 0;
height: 0;
border-bottom: 50px solid red;
border-right: 50px solid transparent;
border-left: 50px solid transparent;
}
</style>
实现一个宽高自适应的正方形
.square {
width: 10%;
height: 10vw;
background: tomato;
}
实现一个扇形
div{
border: 100px solid transparent;
width: 0;
heigt: 0;
border-radius: 100px;
border-top-color: red;
}
flex布局如何实现子元素在右下角
<div class="container">
<div class="item">子元素1</div>
</div>
.container {
display: flex;
justify-content: flex-end; /* 子元素在主轴上右对齐 */
align-items: flex-end; /* 子元素在交叉轴上底部对齐 */
height: 100vh; /* 设置容器高度,可以根据实际情况调整 */
}
.item {
margin: 10px; /* 可以根据实际情况调整子元素之间的间距 */
}
.container {
display: flex;
justify-content: flex-end; /\* 子元素在主轴上右对齐 */
height: 100vh; /* 设置容器高度,可以根据实际情况调整 \*/
}
.item {
align-self: flex-end; /* 将子元素在交叉轴上底部对齐 */
}
二、JS手写函数
Promise
function Promise(executor) {
const self = this; // 保存当前实例对象的this的值
// 添加属性
self.PromiseState = PENDING // 给promise对象指定status属性,初始值为pending
self.PromiseResult = null // 给promise对象指定一个用于存储结果数据的属性
self.callbacks = [] // 存的是对象 每个元素的结构:{onResolved() {}, onRejected() {}}
function resolve(value) {
// 如果当前状态不是pending,直接结束
if (self.PromiseState !== PENDING) return
// 1. 修改对象的状态(promiseState)为 fulfilled
self.PromiseState = RESOLVED
// 2. 设置对象结果值(promiseResult)为 value
self.PromiseResult = value
// 如果有待执行的callback函数,立即【异步】执行回调函数onResolved
if (self.callbacks.length > 0) {
setTimeout(() => { // 放入队列中执行所有成功的回调
self.callbacks.forEach(callbacksObj => {
callbacksObj.onResolved(value)
})
}, 0)
}
}
function reject(reason) {
// 如果当前状态不是pending,直接结束
if (self.PromiseState !== PENDING) return
// 1. 修改对象的状态(promiseState)为 rejected
self.PromiseState = REJECTED
// 2. 设置对象结果值(promiseResult)为 reason
self.PromiseResult = reason
// 如果有待执行的callback函数,立即【异步】执行回调函数onRejected
if (self.callbacks.length > 0) {
setTimeout(() => { // 放入队列中执行所有失败的回调
self.callbacks.forEach(callbacksObj => {
callbacksObj.onRejected(reason)
})
}, 0)
}
}
// 立即【同步】执行executor函数
try {
executor(resolve, reject)
} catch(error) { // 如果执行器抛出异常,promise对象变成rejected状态
reject(error)
}
}
Promise.all
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let count = 0
const len = promises.length
const values = new Array(len)
for (let i = 0; i < len; i++) {
Promise.resolve(promises[i]).then(
value => {
values[i] = value
count++
if (count === len) {
resolve(values)
}
},
reason => {
reject(reason)
}
)
}
})
}
Promise.race
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (var i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(
(value) => {
resolve(value)
},
(reason) => {
reject(reason)
}
)
}
})
}
Promise.resolve()
Promise.resolve = function(value) {
return new Promise((resolve, reject) => {
if(value instanceof Promise) {
value.then(resolve, reject)
} else {
resolve(value)
}
}
}
Promise.reject()
Promise.reject = function(reason) {
return new Promise((resolve, reject)=> {
reject(reason)
})
}
call
// call函数实现
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
apply
// apply 函数实现
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
bind
Function.prototype.myBind = function (context, ...args) {
const fn = this
args = args ? args : []
return function newFn(...newFnArgs) {
if (this instanceof newFn) {
return new fn(...args, ...newFnArgs)
}
return fn.apply(context, [...args,...newFnArgs])
}
}
new
function myNew(Fn, ...args) {
const obj = new Object()
obj.__proto__ = Fn.prototype;
const res = Fn.apply(obj,args);
if(typeof res === 'object' && res !== null){
return res
}else{
return obj
}
}
// 1. 创建新对象 创建空的object实例对象,作为Fn的实例对象
// 2. 修改新对象的原型对象 将Fn的prototype(显式原型)属性赋值给obj的__proto__(隐式原型)属性
// 3. 修改函数内部this指向新对象,并执行
// 4. 返回新对象 与new保持一直,如果构造函数有返回值,返回值是对象a就返回对象a,否则返回实例对象
instance of
function myInstanceof(left, right) {
if (typeof left !== 'object' || left === null) {
return false
}
let proto = Object.getPrototypeOf(left)
while (proto) {
if (proto === right.prototype) {
return true
}
proto = Object.getPrototypeOf(proto)
}
return false
}
Object.create
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
Object.assign
Object.myAssign = function(target, ...source) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object')
}
let ret = Object(target)
source.forEach(function(obj) {
if (obj != null) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
ret[key] = obj[key]
}
}
}
})
return ret
}
setTimeout模拟setInterval
const simulateSetInterval = (func, timeout) => {
let timer = null
const interval = () => {
timer = setTimeout(() => {
// timeout时间之后会执行真正的函数func
func()
// 同时再次调用interval本身,是不是有点setInterval的感觉啦
interval()
}, timeout)
}
// 开始执行
interval()
// 返回用于关闭定时器的函数
return () => clearTimeout(timer)
}
const cancel = simulateSetInterval(() => {
console.log(1)
}, 300)
setTimeout(() => {
cancel()
console.log('一秒之后关闭定时器')
}, 1000)
setInterval模拟setTimeout
const simulateSetTimeout = (fn, timeout) => {
let timer = null
timer = setInterval(() => {
// 关闭定时器,保证只执行一次fn,也就达到了setTimeout的效果了
clearInterval(timer)
fn()
}, timeout)
// 返回用于关闭定时器的方法
return () => clearInterval(timer)
}
const cancel = simulateSetTimeout(() => {
console.log(1)
}, 1000)
// 一秒后打印出1
map
export function map(arr, callback) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i], i)); // 将回调函数执行结果添加到结果数组中
}
return result;
}
reduce
export function reduce(arr, callback, initValue) {
let result = initValue;
for (let i = 0; i < arr.length; i++) {
result = callback(result, arr[i]);
}
return result;
}
filter
export function filter(arr, callback) {
// 定义空数组接收 回调返回为true的 元素
let result = [];
for (let i = 0; i < arr.length; i++) {
let res = callback(arr[i], i);
// 如果回调结果为真 就压入结果数组中
if (res) {
result.push(arr[i]);
}
}
return result;
}
find
export function find(arr, callback) {
for (let i = 0; i < arr.length; i++){
let res = callback(arr[i], i)
if (res) {
return arr[i]
}
}
return undefined
}
findIndex
export function findIndex(arr, callback) {
for (let i = 0; i < arr.length; i++){
let res = callback(arr[i], i)
if (res) {
// 返回 满足条件的第一个元素的小标
return i
}
}
// 如果没有遇到满足条件的就返回 -1
return -1
}
every
/**
* 封装every函数
* @param {Array} arr
* @param {Function} callback
* @returns
*/
export function every(arr, callback) {
for (let i = 0; i < arr.length; i++){
let res = callback(arr[i])
// 只要有一个不满足就返回false
if (!res) {
return false
}
}
return true
}
some
export function some(arr, callback) {
for (let i = 0; i < arr.length; i++){
let res = callback(arr[i])
if (res) {
return true
}
}
return false
}
trim
function myTrim(str) {
let start = 0;
let end = str.length - 1;
while (start <= end && str[start] === ' ') {
start++;
}
while (end >= start && str[end] === ' ') {
end--;
}
return str.slice(start, end + 1);
}
cancat
function concat(arr, ...args) {
const result = [...arr];
args.forEach((item) => {
// 判断item是否是数组,是数组就要展开入栈
if (Array.isArray(item)) {
result.push(...item);
} else {
result.push(item);
}
});
return result;
}
slice
function slice(arr, begin, end) {
if (arr.length === 0) {
return [];
}
begin = begin || 0;
if (begin >= arr.length) {
return [];
}
end = end || arr.length;
if (end > arr.length) {
end = arr.length;
}
if (end < begin) {
return [];
}
const result = [];
for (let i = begin; i < end; i++) {
result.push(arr[i]);
}
return result;
}
flatten
function flatten(arr) {
let result = [];
arr.forEach((item) => {
// 判断是不是数组
if (Array.isArray(item)) {
result = result.concat(flatten(item));
} else {
result = result.concat(item);
}
});
return result;
}
数组分块方法 chunk
function chunk(arr, size = 1) {
if (arr.length === 0) {
return [];
}
let result = [];
let temp = [];
arr.forEach((item) => {
// 这里先推入temp再往temp中推入元素
// 判断temp元素长度是否为0
if (temp.length === 0) {
result.push(temp);
}
// 将元素压入到临时数组temp中
temp.push(item);
// temp满了就清空
if (temp.length === size) {
temp = [];
}
});
return result;
}
数组取差集 difference
function difference(arr1, arr2 = []) {
if (arr1.length === 0) {
return [];
}
if (arr2.length === 0) {
return arr1.slice();
}
const result = arr1.filter((item) => !arr2.includes(item));
return result;
}
删除数组部分元方法 pull pullAll
function pull(arr, ...values) {
const result = [];
for (let i = 0; i < arr.length; i++) {
// 判断arr中当前元素是否存在于values数组中
if (values.includes(arr[i])) {
// 先将该元素存入result数组中
result.push(arr[i]);
// 然后再删除该元素
arr.splice(i, 1);
// 因为删除的元素,下标自减
i--;
}
}
return result;
}
function pullAll(arr, values) {
return pull(arr, ...values);
}
得到数组部分元方法 drop dropRight
function drop(arr, size) {
// return arr.filter((valur, index) => {
// return index >= size;
// });
return arr.filter((value, index) => index >= size);
}
function dropRight(arr, size) {
return arr.filter((value, index) => index < arr.length - size);
}
Loadsh库的set方法
const set = (obj, path, value) => {
path = path.split('.');
if (!obj) obj = {};
let subObj = obj;
for (let i = 0, l = path.length; i < l; i++) {
const key = path[i];
if (i === l - 1) {
subObj[key] = value;
} else {
if (!subObj[key]) subObj[key] = {};
subObj = subObj[key];
}
}
return obj;
}
// set({},'b.c',1) {b:{c:1}}
三、自定义函数
类型判断
function getType(obj) {
const type = typeof obj;
if (type !== "object") {
return type;
} else if (obj === null) {
return "null";
} else {
const typeName = Object.prototype.toString.call(obj);
return typeName.slice(8, -1).toLowerCase(); // 去除 "[object" 和 "]" 部分
}
}
深拷贝
function deepClone(obj) {
const cache = new WeakMap();
function _deepClone(value) {
if (value === null || typeof value !== 'object') {
return value;
}
if (value instanceof Date) return new Date(value);
if (value instanceof RegExp) return new RegExp(value);
if (cache.has(value)) {
return cache.get(value);
}
let cloneObj;
if (value instanceof Array) {
cloneObj = [];
} else {
cloneObj = new value.constructor();
}
cache.set(value, cloneObj);
for (let key in value) {
if (value.hasOwnProperty(key)) {
cloneObj[key] = _deepClone(value[key]);
}
}
return cloneObj;
}
return _deepClone(obj);
}
深比较
function isObject(obj) {
return typeof obj === object && obj !== null;
}
// 深度比较
function isEqual(obj1, obj2) {
if (!isObject(obj1) || !isObject(obj2)) {
// 值类型,直接判断【一般不会传函数,不考虑函数】
return obj1 === obj2;
}
if (obj1 === obj2) {
return true;
}
// 两个都是对象或数组,而且不相等
// 1. 先判断键的个数是否相等,不相等一定返回false
const obj1Keys = Object.keys(obj1);
const obj2Keys = Objext.keys(obj2);
if (obj1Keys.length !== obj2Keys.length) {
return false;
}
// 2. 以obj1为基准,和obj2依次递归比较
for (let key in obj1) {
// 递归比较
const res = isEqual(obj1[key], obj2[key]);
if (!res) {
return false;
}
}
// 3. 全相等
return true;
}
自定义合并对象mergeObject
/**
* 合并多个对象,返回一个合并后的对象,不改变原对象
* @param {...any} objs
* @returns
*/
function mergeObject(...objs) {
const result = {};
// 遍历objs得到一个个obj
objs.forEach((obj) => {
// 遍历obj的键得到一个个key
Object.keys(obj).forEach((key) => {
// 判断result对象中有没有key值属性
if (!result.hasOwnProperty(key)) {
// 如果没有,就将obj中的key值属性添加到result中
result[key] = obj[key];
} else {
// 如果result有了,就合并属性
result[key] = [].concat(result[key], obj[key]);
}
});
});
return result;
}
节流
function throttle(func, delay) {
let timerId = null;
return function (...args) {
if (timerId) return;
timerId = setTimeout(() => {
func.apply(this, args);
timerId = null;
}, delay);
}
}
防抖
function debounce(func, delay) {
let timerId = null //定时器变量
return function (...args) {
if (timerId) clearTimeout(timerId); //有定时器,就清空,取消定时器,阻止回调执行
timerId = setTimeout(() => {
func.apply(this, args);
timerId = null
}, delay);
}
}
继承
一次性执行函数
function once(fn) {
return fucntion (...args) {
if(fn) {
const ret = fn.apply(this, args)
fn = null
return ret
}
}
}
柯里化
function curry(fn,...args){
let fnLen = fn.length,
argsLen = args.length;
//对比函数的参数和当前传入参数
//若参数不够就继续递归返回curry
//若参数够就调用函数返回相应的值
if(fnLen > argsLen){
return function(...arg2s){
return curry(fn,...args,...arg2s)
}
}else{
return fn(...args)
}
}
AJax
function ajax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest() //创建一个异步对象
xhr.open('get', url, true) //设置请求方式和请求地址
xhr.onreadystatechange = function () { //通过onreadystatechange监听状态变化
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText) //处理返回的结果
} else {
reject(new Error(xhr.responseText))
}
}
}
xhr.send(null)//发送请求
})
}
实现数组去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
return Array.from(new Set(arr))
return [...new Set(arr)]
}
function unique(arr) {
if (!Array.isArray(arr)) {
return [];
}
const res = [];
arr.forEach((item, index) => {
if (res.indexOf(item) === -1) {
res.push(item);
}
});
return res;
}
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
arr = arr.sort()
let res = [arr[0]]
for (let i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
res.push(arr[i])
}
}
return res
}
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
return arr.filter((item,index,arr) => {
return arr.indexOf(item)===index
})
}
随机数
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
字符串翻转
function reverseString(str) {
// 将字符串转换成数组
let arr = str.split("");
// 使用数组的翻转方法
arr.reverse();
// 将数组拼接成字符串
let s = arr.join("");
return s;
}
检测回文字符串
function palindrome(str) {
return reverseString(str) === str;
}
截取字符串
function truncate(str, size) {
return str.slice(0, size) + "...";
}
四、场景题目
异步控制并发数
// 并发请求函数
const concurrencyRequest = (urls, maxNum) => {
return new Promise((resolve) => {
if (urls.length === 0) {
resolve([]);
return;
}
const results = [];
let index = 0; // 下一个请求的下标
let count = 0; // 当前请求完成的数量
// 发送请求
async function request() {
if (index === urls.length) return;
const i = index; // 保存序号,使result和urls相对应
const url = urls[index];
index++;
console.log(url);
try {
const resp = await fetch(url);
// resp 加入到results
results[i] = resp;
} catch (err) {
// err 加入到results
results[i] = err;
} finally {
count++;
// 判断是否所有的请求都已完成
if (count === urls.length) {
console.log('完成了');
resolve(results);
}
request();
}
}
// maxNum和urls.length取最小进行调用
const times = Math.min(maxNum, urls.length);
for(let i = 0; i < times; i++) {
request();
}
})
}
实现每隔一秒打印 1,2,3,4
// 使用闭包实现
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
// 使用 let 块级作用域
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
图片懒加载
mport { throttle } from 'lodash';
const LazyLoad = {
// install方法 将其注册为Vue插件
install(Vue,options){
// 默认的 loading 图片路径
let defaultSrc = options.default;
//自定义地指令
Vue.directive('lazy',{
// 当指令绑定到元素上时触发 binding绑定的值
bind(el,binding){
//初始化元素
LazyLoad.init(el,binding.value,defaultSrc);
},
// 当绑定的元素插入到父节点时触发
inserted(el){
// 兼容处理 判断浏览器是否支持 IntersectionObserver
if('IntersectionObserver' in window){
// 使用 IntersectionObserver 监听元素
LazyLoad.observe(el);
}else{
// 使用滚动监听来实现懒加载
LazyLoad.listenerScroll(el);
}
},
})
},
// 初始化
init(el,val,def){
// data-src 储存真实src
el.setAttribute('data-src',val);
// 设置src为loading图
el.setAttribute('src',def);
},
// 利用IntersectionObserver监听el
observe(el){
//IntersectionObserver 是一个用于异步监听目标元素与其祖先元素或视窗(viewport)交叉状态的 API。它提供了一种有效的方式来检测元素是否进入或离开视窗可见区域,或者与包含它的容器发生交叉。
// 创建一个 IntersectionObserver 实例
let io = new IntersectionObserver(entries => {
let realSrc = el.dataset.src;
if(entries[0].isIntersecting){
if(realSrc){
// 当元素进入可视区域时,将真实的图片地址赋值给 `src` 属性
el.src = realSrc;
el.removeAttribute('data-src');
}
}
});
io.observe(el);
},
// 使用滚动监听来实现懒加载
listenerScroll(el){
// 定义节流函数
let handler = throttle(LazyLoad.load,300);
// 初始化加载元素
LazyLoad.load(el);
// 监听滚动事件
window.addEventListener('scroll',() => {
handler(el);
});
},
// 加载真实图片
load(el){
// 获取窗口高度
let windowHeight = document.documentElement.clientHeight
// 获取元素相对于视口的位置信息
let elTop = el.getBoundingClientRect().top;
let elBtm = el.getBoundingClientRect().bottom;
// 获取真实的图片地址
let realSrc = el.dataset.src;
if(elTop - windowHeight<0&&elBtm > 0){
if(realSrc){
// 当元素在可视区域内时,将真实的图片地址赋值给 `src` 属性
el.src = realSrc;
el.removeAttribute('data-src');
}
}
},
}
export default LazyLoad;
循环打印红黄绿
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
const task = (timer, light, callback) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
callback()
}, timer)
}
const step = () => {
task(3000, 'red', () => {
task(2000, 'green', () => {
task(1000, 'yellow', step)
})
})
}
step()
const task = (timer, light) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
const step = () => {
task(3000, 'red')
.then(() => task(2000, 'green'))
.then(() => task(2100, 'yellow'))
.then(step)
}
step()
const taskRunner = async () => {
await task(3000, 'red')
await task(2000, 'green')
await task(2100, 'yellow')
taskRunner()
}
taskRunner()