手撕代码

64 阅读10分钟

一、CSS布局

垂直居中

  1. 定位+margin+计算

    //父盒子宽度的一半减去子盒子宽度的一半 500/2 - 200/2 = 150
    //父盒子高度的一半减去子盒子高度的一半 500/2 - 200/2 = 150
    .parent {
      position: relative;
    }
    
    .child {
      position: absolute;
      margin-top:150px;
      margin-left:150px;
    }
    
  2. 定位+margin+负值

    .child {
    	top: 50%;
        left: 50%;
        margin-top:-50px;
        margin-left:-50px;
    }
    
  3. 定位+margin+transform(不需要知道宽高)

    .child {
    	top: 50%;
        left: 50%;
    	transform: translate(-50%, -50%);
    }
    
  4. 定位+margin+auto(不需要知道宽高)

    .child {
      top: 0px;
      left: 0px;
      bottom: 0px;
      right: 0px;
      margin: auto;
    }
    
  5. flex布局(不需要知道宽高)

    //将父盒子设置成弹性盒容器 让子元素水平居中,垂直居中
    .parent {
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
  6. table布局(不需要知道宽高)

    //设置父元素为display:table-cell,子元素设置 display: inline-block。
    //利用vertical和text-align可以 让所有的行内块级元素水平垂直居中
    .parent{
        display: table-cell;
        vertical-align: middle;
        text-align: center;
    }
    .child{
        display: inline-block;
    }
    
  7. grid网格布局(不需要知道宽高)(兼容性比较差)

    .parent {
      display: grid;
      justify-content: center;
      align-items: center;
    }
    
  8. 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>
    

两栏布局

  1. 一个定宽 一个自适应
  • 左边栏使用 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>
  1. 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负值】

  1. 左右开启浮动+清除浮动
  2. 主区域两侧留白【margin】
  3. 两侧上去【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负值+定位】

  1. 开启浮动+清除浮动
  2. 主区域两侧留白 【padding】
  3. 两侧上去【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()