前端面试-手写代码

705 阅读14分钟

前端面试-手写代码

1. 防抖节流

防抖:等待wait时间后再执行该事件,若在wait时间内被重复触发,则重新计时。如果短时间内大量触发同一事件,只会执行一次函数。防抖经常用在搜索框输入搜索,只在输入完后,才执行查询的请求。

节流:如果短时间内大量触发同一事件,那么delay时间内只运行一次,若在delay时间内重复触发,只有一次生效,直至过了这段时间才重新生效。节流一般在用在鼠标点击,滚动事件等,防止过多的请求造成服务器压力。

// 函数防抖的实现
function debounce(fn, wait) {
    let timer = null;
    return function() {
        let context = this, args = arguments;
        // 如果此时存在定时器的话,则取消之前的定时器重新记时
        if (timer) {
            clearTimeout(timer);
        }
        // 设置定时器,使事件间隔指定事件后执行
        timer = setTimeout(() => {
            fn.apply(context, args);
        }, wait);
    };
}

// 函数节流的实现;
function throttle(fn, delay) {
    let curTime = Date.now();
    return function() {
        let context = this,
            args = arguments,
            nowTime = Date.now();
        // 如果两次时间间隔超过了指定时间,则执行函数。
        if (nowTime - curTime >= delay) {
            curTime = Date.now();
            fn.apply(context, args);
        }
    };
}

2. 手写call,apply,bind函数

1. call、apply、bind三个方法都有两个参数;第一个参数都是this(对象).
2. call和apply都是立即执行函数,不必调用。call第二个参数不用数组接收,apply用数组接收,类型可以是任意类型**.
3. bind返回值是一个函数(不会立即执行,需要调用),传参和call一样
// call函数实现
Function.prototype.myCall = function (context,...args) {
    if(typeof this !== 'function') { // this必须是函数
        throw new TypeError('Must be a function')
    }
    if(!context) context = window // 没有context,或者传递的是null undefined,则重置为window
    context.fn = this // 将this添加到context的属性上
    const result = context.fn(...args) // 直接调用context的fn
    delete context.fn // 删除掉context新增的属性
    return result // 返回返回值
}

// apply 函数实现
Function.prototype.myApply = function (context,args=[]) {
    if(typeof this !== 'function') { // this必须是函数
        throw new TypeError('Must be a function')
    }
    if(!context) context = window // 没有context,或者传递的是null undefined,则重置为window
    context.fn = this // 将this添加到context的属性上
    const result = context.fn(...args) // 直接调用context的fn
    delete context.fn // 删除掉context新增的属性
    return result // 返回返回值
}

// bind 函数实现
Function.prototype.myBind = function (context, ...args) {
    if(typeof fn !== 'function'){
        throw new TypeError('It must be a function');
    }
    if(!context) context = window;
    const fn = this;
    return function (...otherArgs) {
        return fn.apply(context, [...args, ...otherArgs]);
    };
};

// 测试代码
function show(...args) {
    console.log(args)
    console.log(this.name)
}
show.myBind({name:'HSY'},'YT','YF','LQH')

3. 函数柯里化

定义:函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术

function curry(fn, args) {
// 获取函数需要的参数长度
    let length = fn.length;
    args = args || [];
    return function() { 
        let subArgs = args.slice(0); 
        // 拼接得到现有的所有参数 
        for (let i = 0; i < arguments.length; i++) { 
            subArgs.push(arguments[i]); 
        } 
        // 判断参数的长度是否已经满足函数所需参数的长度
        if (subArgs.length >= length) {
            // 如果满足,执行函数 
            return fn.apply(this, subArgs);
        } else {
            // 如果不满足,递归返回科里化的函数,等待参数的传入
            return curry.call(this, fn, subArgs);
        } 
    };
}

// es6 实现 
function curry(fn, ...args) { 
    return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args); 
}

4. 实现深拷贝

function deepCopy(object) {
    if (!object || typeof object !== "object") return;
    let newObject = Array.isArray(object) ? [] : {};
    for (let key in object) {
        if (object.hasOwnProperty(key)) {
            newObject[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
        }
    }
    return newObject;
}

5. 数组扁平化

var arr = [1,2,[3,4,5,[6,7,8],9],10,[11,12]]
// 数组平铺递归写法
function flatten (arr) {
    let result = []
    for(let i=0;i<arr.length;i++) {
        if(Array.isArray((arr[i]))) {
            result = result.concat(flatten(arr[i]))
        } else {
            result.push(arr[i])
        }
    }
    return  result
}

// reduce实现
function flatten (arr) {
    return arr.reduce((prev,next)=> {
        return prev.concat(Array.isArray(next)?flatten(next):next)
    },[])
}

// ES6 中的 flat,参数可以传递数组的展开深度
function flatten(arr) {
    return arr.flat(Infinity);
}

字符串写法
function flatten (arr) {
    return arr.toString().split(',').map((item) => parseInt(item))
}

// 指定层级的数组扁平化
function flatten(arr, level) {
    function walk(arr, currLevel) {
        let res = [];
        for (let item of arr) {
            if (Array.isArray(item) && currLevel < level) {
                res = res.concat(walk(item, currLevel + 1));
            } else {
                res.push(item)
            }
        }
        return res;
    }
    return walk(arr, 1);
}

const arr = [1,2,3,[4,5,[6],7],8]

var res = flatten(arr, 3);
console.log(res);

6. 手写instanceof

function myInstanceof(left,right) {
    let proto = Object.getPrototypeOf(left) // 获取对象的原型
    let prototype = right.prototype // 获取构造函数的 prototype 对象
    // 判断构造函数的 prototype 对象是否在对象的原型链上
    while(true) {
        if(!proto) return false
        if(proto===prototype) return true
        proto = Object.getPrototypeOf(proto)
    }
}
console.log(myInstanceof([],Array))

7. 数组去重

// 1. ES6方法(使用数据结构集合)
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
let newArray = Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
console.log(newArray)

// 2. ES5 Map
function uniqueArray(array) {
    let map = {};
    let res = [];
    for(var i = 0; i < array.length; i++) {
        if(!map.hasOwnProperty([array[i]])) {
            map[array[i]] = 1;
            res.push(array[i]);
        }
    }
    return res;
}
console.log(uniqueArray(array)); // [1, 2, 3, 5, 9, 8]

// 3. 获取数组中重复的元素,利用两个set
function duplicates(arr) {
    let set = new Set()
    let ans = new Set()
    for(let item of arr) {
        if(set.has(item)) {
            ans.add(item)
        } else {
            set.add(item)
        }
    }
    return Array.from(ans)
}

8. 手写new操作符

在调用 new 的过程中会发生以下四件事情:

(1)首先创建了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

function create () {
    // 1. 创建新的对象。
    let obj = new Object()
    // 2.利用 shift 拿到 arguments 的第一个参数,也就是构造函数。
    const Func = [].shift.call(arguments)
    // 3.将新对象的 __proto__ 与构造函数的 prototype 挂钩。
    //obj.__proto__ = Func.prototype
    Object.setPrototypeOf(obj, Func.prototype)
    // 4.将新对象作为上下文执行构造函数。
    const Res = Func.apply(obj, arguments)
    // 5.判断返回值是否为对象,若是对象则返回该对象,否则返回新对象。
    return Res instanceof Object ? Res : obj
}

function Person (name) {
    this.name = name || '我是名字'
}
Person.prototype.sayName = function () {
    console.log('sayName >>>', this.name)
}

let p1 = create(Person, '老李')
p1.sayName() // sayName >>> 老李

9. 手写sleep

// sleep是一种函数,他的作用是使程序暂停指定的时间,起到延时的效果。
function sleep(time) {
    return new Promise(resolve => {
        console.log(resolve)
        setTimeout(resolve, time)
    })
}

sleep(2000).then(res => {
    console.log(`sleep exe ending!`)
})

10. 创建AJAX请求

// 1. 创建 XMLHttpRequest 实例
let xhr = new XMLHttpRequest()
// 2. 打开和服务器的连接
xhr.open('get','url',true)
// 3.发送,由于是异步的,所以send函数可以放在前面
xhr.send()
// 4. 接收变化
xhr.onreadystatechange = () => {
    if(xhr.readyState==4&&xhr.status==200) {
        console.log(xhr.responseText);   //响应主体
    }
}
// 使用promise封装AJAX
const p = new Promise((resolve, reject) => {
    //1. 创建对象
    const xhr = new XMLHttpRequest();
    //2. 初始化
    xhr.open("GET", "https://api.apiopen.top/getJ");
    //3. 发送
    xhr.send();
    //4. 绑定事件, 处理响应结果
    xhr.onreadystatechange = function() {
        //判断
        if (xhr.readyState === 4) {
            //判断响应状态码 200-299
            if (xhr.status >= 200 && xhr.status < 300) {
                //表示成功
                resolve(xhr.response);
            } else {
                //如果失败
                reject(xhr.status);
            }
        }
    }
})

11. 循环中使用闭包解决var定义函数的问题

// 原写法
for (var i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i)
    }, i * 1000)
}
// 使用闭包的方式解决
for (var i = 1; i <= 5; i++) {
    (function(j) {
        setTimeout(function timer() {
            console.log(j)
        }, j * 1000)
    })(i)
}

12. 删除对象中所有的函数(涉及JSON循环引用问题)

// 1、给定一个对象,如何去除对象中所有类型为Function的属性
let obj = {
    p1: function(){},
    p2: [function(){}, 123],
    p3: {
        p4: function(){}
    }
}
obj.p5 = obj;
obj.p3.p6 = obj.p2;
// 声明cache变量,便于匹配是否有循环引用的情况
let cache = [];
let str = JSON.stringify(obj, function(key, value) {
    if (typeof value === 'object' && value !== null) {
        if (cache.indexOf(value) !== -1) {
            // 移除
            return;
        }
        // 收集所有的值
        cache.push(value);
    }
    return value;
});
console.log(JSON.parse(str))

13. 手写url参数解析获取

function getParams(url) {
    let res = {}
    if (url.includes('?')) {
        let str = url.split('?')[1]
        let arr = str.split('&')
        for (let item of arr) {
            let key = item.split('=')[0]
            let value = item.split('=')[1]
            res[key] = decodeURIComponent(value) // decodeURIComponent()可对encodeURIComponent()函数编码的URI进行解码
        }
    }
    return res
}

let result = getParams('http://www.baidu.com?user=%E9%98%BF%E9%A3%9E&age=16')
console.log(result)

14. 实现数组的map,filter,reduce方法

// 1. map方法
Array.prototype._map = function(fn) {
   if (typeof fn !== "function") {
        throw Error('参数必须是一个函数');
    }
    const res = [];
    for (let i = 0, len = this.length; i < len; i++) {
        res.push(fn(this[i], i, this);
    }
    return res;
}

// 2. filter方法
Array.prototype._filter = function(fn) {
    if (typeof fn !== "function") {
        throw Error('参数必须是一个函数');
    }
    const res = [];
    for (let i = 0, len = this.length; i < len; i++) {
        fn(this[i], i, this) && res.push(this[i]);
    }
    return res;
}

// 3. reduce方法
Array.prototype._reduce = function (fn, initalValue) {
    if (typeof fn !== "function") {
        throw Error('参数必须是一个函数');
    }
    let i = initalValue !== undefined ? 0 : 1
    let result = initalValue !== undefined ? initalValue : this[0]
    for (; i < this.length; i++) {
        result = fn(result, this[i], i, this)
    }
    return result
}

15. react hook封装一个倒计时组件(函数式组件)

function Reminder(props) {
  const [timerID, setTimerID] = useState(null);
  const [counter, setCounter] = useState(props.duration);
  useEffect(() => {
    if(counter > 0){
      let timer = setTimeout(() => {
        setCounter(counter-1)
      }, 1000);
      setTimerID(timer)
    }else{
      console.log('倒计时结束');
    }
    // useEffect清除副作用
    return () => {
      setTimerID(null);
    }
  },[counter]);
  return (
    <div>
      <p>{counter}秒后将自动跳转至订单页面...</p>
    </div>
  );
}

16. 手写promise

// 手写promise
// 1. 初始化结构,包括定义三种状态;constructor传入一个函数,并执行;resolve和reject函数以及结果变量result
// 2. 解决this指向问题,通过.bind绑定this
// 3. then方法,传入两个函数,成功和失败分别执行对应的函数。需要解决传入的参数不是函数的问题,手动给一个空函数
// 4. 异常抛出解决 try catch解决
// 5. 异步功能实现 在then函数中添加setTimeout,并解决then方法比resolve先执行的问题,用回调数组存储then里面的回调函数,在resolve和reject函数里面遍历调用
// 6. resolve和reject函数需要在事件循环的末尾执行的,需要添加setTimeout
// 7. 链式功能的解决,返回一个promise(存在问题,建议先别写)
class MyPromise {
    static PENDING = '待定'; FULFILLED = '成功';REJECTED = '失败'
    constructor(func) {
        this.status = MyPromise.PENDING
        this.result = null // 存放结果
        this.resolveCallbacks = [] // 存放resolve里面的回调函数,用于异步处理(then比resolve先执行的情况)
        this.rejectCallbacks = []
        // 抛出错误时,执行reject函数
        try {
            func(this.resolve.bind(this),this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        setTimeout(()=> {
            if(this.status===MyPromise.PENDING) {
                this.status = MyPromise.FULFILLED
                this.result = result
                this.resolveCallbacks.forEach(callback=> {
                    callback(result)
                })
            }
        })
    }
    reject(result) {
        setTimeout(()=> {
            if(this.status===MyPromise.PENDING) {
                this.status = MyPromise.REJECTED
                this.result = result
                this.rejectCallbacks.forEach(callback=> {
                    callback(result)
                })
            }
        })
    }
    then(onFULFILLED,onREJECTED) {
        return new MyPromise((resolve,reject)=> {
            onFULFILLED = typeof onFULFILLED==='function'?onFULFILLED:()=>{}
            onREJECTED = typeof onREJECTED==='function'?onREJECTED:()=>{}
            if(this.status === MyPromise.PENDING) {
                this.resolveCallbacks.push(onFULFILLED)
                this.rejectCallbacks.push(onREJECTED)
            }
            if(this.status === MyPromise.FULFILLED) {
                setTimeout(()=> {
                    onFULFILLED(this.result)
                })
            }
            if(this.status === MyPromise.REJECTED) {
                setTimeout(()=> {
                    onREJECTED(this.result)
                })
            }
        })
    }
}

// 测试代码
console.log('第一步')
let pro = new MyPromise((resolve,reject) => {
    console.log('第二步')
    setTimeout(()=>{
        resolve('这次一定')
        reject('下次一定')
        console.log('第四步')
    })
})
pro.then(
    result=> {console.log(result)},
    result=> {console.log(result.message)}
)
console.log('第三步')

promise.all

function promiseAll(promises) {
    return new Promise(function(resolve, reject) {
        if(!Array.isArray(promises)){
            throw new TypeError('argument must be a array')
        }
        let resolvedCounter = 0;
        let promiseNum = promises.length;
        let resolvedResult = [];
        for (let i = 0; i < promiseNum; i++) {
            Promise.resolve(promises[i]).then(value=>{
                resolvedCounter++;
                resolvedResult[i] = value;
                if (resolvedCounter == promiseNum) {
                    return resolve(resolvedResult)
                }
            },error=>{
                return reject(error)
            })
        }
    })
}
// test
let p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(2)
    }, 2000)
})
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(3)
    }, 3000)
})
promiseAll([p3, p1, p2]).then(res => {
    console.log(res) // [3, 1, 2]
})

Promise.race

该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能改变一次, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可

function promiseRace(promises) {
  return new Promise((resolve, reject) => {
    for(let i = 0; i < promises.length; i++) {
      promises[i].then(resolve, reject);
    }
  })
}

17. 包装一个高阶函数,模拟发送请求 ,只取最后一次的结果,前面的promise还没完成的话就取消

场景:有一个下拉框,选择项比如说是 1~6 的数字,每点一下下拉框中的数字就发起一个异步请求并根据相应的数据渲染页面,这里面都有哪些坑,怎样解决?

  • 坑 1:如果点了几个选项,会有几个异步请求发出去,那么无法确定哪个异步请求先响应,渲染的顺序无法控制。(取消上一次的异步请求)
  • 坑 2:如果用户连续点很多选项,有太多的异步请求发出去,实际上我们只需要渲染其中一个就可以了。(节流处理)
/**
 *  解决异步操作冲突的问题
 *  多次触发后,丢弃未响应的结果,只有最新的一次会进入 then回调中
 *  @template T
 *  @param {T & function} fn 需要包装的异步操作
 *  @return T
 */
export function useLatestCall(fn) {
    //  “假” promise 中的 reject 方法
    let prePromiseReject
    return function() {
        // 如果存在,就行终止
        prePromiseReject?.(new Error('新的promise执行,旧promise被丢弃'))
        // 创建“假” promise,并将reject赋值给闭包变量
        const fakePromise = new Promise((resolve, reject) => (prePromiseReject = reject))
        return Promise.race([fakePromise, fn(...arguments)])
            .finally(() => (prePromiseReject = null))
    }
}
// 业务中使用(vue 或者 react中具体写法不同,大致如此):
const fetchData = useLatestCall(getXXXXX)
const reloadPage = () => {
    // 只有最近触发的一次会进入 then 回调
    fetchData(params).then(res => {
        // xxx
    })
}

下面是未验证方案

function wrap(fn) {
    // your code
    // 类似于防抖节流的区别
    let isEnd = false
    let timer;
    return function() {
        clearTimeout(timer)
        timer = setTimeout(()=> isEnd = true)
        return new Promise(rs=> {
            //节流
            setTimeout(()=> isEnd ? rs(fn()) : fn())
            //防抖
            setTimeout(()=> isEnd && rs(fn()))
        })
    }
}

let count=0;
function sendRequest(){
    return new Promise((resolve)=>{
        setTimeout(()=>{
            resolve(++count)
        })
    });
}
let newWrap = wrap(sendRequest);
newWrap().then(console.log)
newWrap().then(console.log)
newWrap().then(console.log) //输出3

18. 手写双向绑定(数据响应式)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script>
    const data = {
        name: 'vue',
        age: 19
    }
    class Dep {
        constructor() {
            this.subs = []
        }
        addSub(watcher) {
            this.subs.push(watcher)
        }
        notify() {
            this.subs.forEach(item=> {
                item.update()
            })
        }
    }
    class Watcher {
        constructor(name) {
            this.name = name
        }
        update() {
            console.log(this.name+'发生Updated')
        }
    }
    Object.keys(data).forEach(key => {
        let value = data[key]
        let dep = new Dep()
        Object.defineProperty(data,key,{
            get: function () {
                console.log('获取'+key+'对应的值')
                let watcher = new Watcher('张三')
                dep.addSub(watcher)
                return value
            },
            set: function (newValue) {
                console.log('监听'+key+'改变')
                dep.notify()
                value = newValue
            }
        })
    })
</script>
<body>

</body>
</html>

19. ES5中的继承

// 1. 构造函数继承
// 缺点:原型链上的方法和属性没法继承
// function Person() {
//     this.name = 'person'
//     this.say = function () {
//         console.log('my name is person')
//     }
// }
// Person.prototype.work = function () {
//     console.log('干饭')
// }
// function Student() {
//     Person.call(this)
//     this.id = '2022'
// }
// var stu = new Student()
// console.log(stu.name)
// console.log(stu.say())
// console.log(stu.work())


// 2. 原型链继承
// 缺点:实例化原型对象的属性是引用类型的时候会出现数据污染的问题(类似浅拷贝问题)
// function Person() {
//     this.name = 'person'
//     this.arr = [1,2]
//     this.say = function () {
//         console.log('my name is person')
//     }
// }
// Person.prototype.work = function () {
//     console.log('干饭')
// }
// function Student() {
//     this.id = '2022'
// }
// Student.prototype = new Person()
// var stu1 = new Student()
// var stu2 = new Student()
// stu1.arr.push(3)
// console.log(stu1.arr,'stu1')
// console.log(stu2.arr,'stu2')
// console.log(stu1.name)
// console.log(stu1.say())
// console.log(stu1.work())

// 3. 组合式继承
function Person() {
    this.name = 'person'
    this.arr = [1,2]
    this.say = function () {
        console.log('my name is person')
    }
}
Person.prototype.work = function () {
    console.log('干饭')
}
function Student() {
    Person.call(this)
    this.id = '2022'
}
// Student.prototype = new Person() // 这样构造函数会执行两次,可以优化
Student.prototype = Object.create(Person.prototype) // 此时构造函数的指向会出现问题,需要修改,Object.create是进行原型的隔离,不然两个对象的原型会保持相同
Student.prototype.constructor = Student
console.log(Student.prototype.constructor)
var stu1 = new Student()
var stu2 = new Student()
stu1.arr.push(3)
console.log(stu1.arr,'stu1')
console.log(stu2.arr,'stu2')
console.log(stu1.name)
console.log(stu1.say())
console.log(stu1.work())

20. js对象增加迭代器,使得对象可以使用for....of方法

// 用generator函数,返回[key,value]
Object.prototype[Symbol.iterator] = function* iterEntries() {
    let keys = Object.keys(this);
    for (let i = 0; i < keys.length; i++) {
        let key = keys[i];
        yield [key, obj[key]];
    }
}

var obj = { a: 'hello', b: 'world', c: 'hello world' }

for (let [key, value] of obj) {
    console.log(key, value);
}

21. 列表转树, 树转列表

// 列表转树
let data = [    { id: 3, parentId: 1},    { id: 2, parentId: 1},    { id: 4, parentId: 2},    { id: 5, parentId: 4},    { id: 6, parentId: 5},    { id: 7, parentId: 3},    { id: 1, parentId: 0},  ]
  // 转成
  //   {
  //     id: 1,
  //     parentId: 0,
  //     children: [
  //       {
  //         id: 2,
  //         parentId: 1
  //       }
  //     ]
  //   }
  function listToTree(data) {
      const map = {};
      let treeData = {};
      for(let i = 0; i < data.length; i++) {
          map[data[i].id] = data[i];
      }
      for(let id in map) {
          let parentId = map[id].parentId;
          if(parentId) {
              if(!map[parentId].children) {
                  map[parentId].children = [];
              }
              map[map[id].parentId].children.push(map[id]);
          } else {
              treeData = map[id];
          }
      }
      return treeData;
  }
  console.log(JSON.stringify(listToTree(data)));
// 树转列表
let tree = {"id":1,"parentId":0,"children":[{"id":2,"parentId":1,"children":[{"id":4,"parentId":2,"children":[{"id":5,"parentId":4,"children":[{"id":6,"parentId":5}]}]}]},{"id":3,"parentId":1,"children":[{"id":7,"parentId":3}]}]}

function treeToList(tree) {
    const res = [];
    let dfs = function(tree) {
        if(tree.children) {
            tree.children.forEach(item => {
                dfs(item);
                delete item.children;
            })
            delete tree.children;  // 第一层
        }
        res.push(tree);
    }
    dfs(tree);
    return res;
}
console.log(treeToList(tree));

22. 对象转树形结构

// 对象转树形结构
let strarr = {
    'a-b-c-d':1,
    'a-b-c-e':2,
    'a-b-f':3,
    'a-j':4
}
// 结果如下
let obj = {
    a:{
        b:{
            c:{
                d:1,
                e:2
            },
            f:3
        },
        j:4
    }
}
const convertToTree = (obj) => {
    let newObj = new Object()
    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
            let arr = key.split("-")
            let val=obj[key]
            let res = newObj
            for(let i=0;i<arr.length-1;i++){
                if(res[arr[i]]) {
                    res=res[arr[i]]
                }
                else{
                    res[arr[i]]={}
                    res=res[arr[i]]
                }
            }
            res[arr[arr.length-1]]=val
        }
    }
    return newObj
}
console.log(convertToTree(strarr))

23. 将DOM节点元素转换成JSON字符串

<div class="root">
    <div class="child1"><p></p></div>
    <span class="span1"><span></span></span>
    <div><div><p class="p1"></p></div></div>
    <p><span></span></p>
</div>

<script>
    function convertToJson() {
        const root = document.getElementsByClassName('root')[0];
        const output = new Object();
        output.tagName = root.tagName;
        output.className = root.className;
        output.childs = getChilds(root);
        console.log(JSON.parse(JSON.stringify(output)));
    }
    function getChilds(node) {
        const childs = node.children;
        const result = new Array();
        if(!childs || childs.length === 0) return result;
        for (const child of childs) {
            const childOutput = new Object();
            childOutput.tagName = child.tagName;
            childOutput.className = child.className;
            childOutput.childs = getChilds(child);
            result.push(childOutput);
        }
        return result;
    }
    convertToJson();
</script>

24. JS实现单例模式

// 在生成某各对象时先判断是否存在。存在就不生成对象,不存在就生成对象
var PersonSingleton = (function(){
    var instance;
    function init(){
        return {
            name: 'HSY',
            work: function(){
                console.log(this.name + ' working');
            }
        };
    }
    return {
        getInstance: function() {
            if(!instance){
                instance = init();
            }
            return instance;
        }
    }
})()

var p1 = PersonSingleton.getInstance();
p1.work();

var p2 = PersonSingleton.getInstance();
p2.work();

25. 常用正则表达式

1. 密码强度验证(8个字符以上,至少一个大写字母,一个小写字母,一个数字)
.*(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d).{8,}

2. 由数字和26个字母组成的字符串
^[A-Za-z0-9]+$

26. 洋葱模型

洋葱模型:方法的执行像洋葱一样,一层一层往里执行,直到中心点后,再一层一层往外出来。

  function asyncFn() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("delay...");
        resolve();
      }, 1000);
    });
  }
  
  const fn1 = async (next) => {
    console.log(1)
    await next()
    console.log(2)
  }
  
  const fn2 = async (next) => {
    console.log(3)
    await asyncFn();
    await next()
    console.log(4)
  }
  
  const fn3 = async (next) => {
    console.log(5)
    await next()
    console.log(6)
  };
  // 关键代码
  function compose(middleware) {
    return function (args) {
      dispatch(0);
      function dispatch(index) {
        const fn = middleware[index] || args;
        if (typeof fn !== "function") return Promise.resolve();
        const next = () => dispatch(index + 1);
        // 给执行函数添加返回成功的Promise.resolve
        return Promise.resolve(fn(next))
      }
    }
  };

  compose([mid1, mid2, mid3])();

27. JS定时器(setTimeout / setInterval)定时不准问题解决方案

1. 动态计算时差 (仅针对循环定时,只起修正作用 )

  • 在定时器开始前和运行时动态获取当前时间,在设置下一次定时时长时,在期望值基础上减去当前时延,以获得相对精准的定时运行效果。
  • 此方法仅能消除setInterval()长时间运行造成的误差累计,但无法消除单个定时器执行延迟问题。
var count = count2 = 0;
var runTime,runTime2;
var startTime,startTime2 = performance.now();//获取当前时间

//普通任务-对比
setInterval(function(){
    runTime2 = performance.now();
    ++count2;
    console.log("普通任务",count2 + ' --- 延时:' + (runTime2 - (startTime2 + count2 * 1000)) + ' 毫秒');
}, 1000);

//动态计算时长
function func(){
    runTime = performance.now();
    ++count;
    let time = (runTime - (startTime + count * 1000));
    console.log("优化任务",count2 + ' --- 延时:' + time +' 毫秒');
    //动态修正定时时间
    t = setTimeout(func,1000 - time);
}
startTime = performance.now();
var t = setTimeout(func , 1000);

//耗时任务
setInterval(function(){
    let i = 0;
    while(++i < 100000000);
}, 0);

2. 使用 Web Worker

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

   <!-- index.html -->
   <html>
   <meta charset="utf-8">
   <body>
   <script type="text/javascript">
   var count = 0;
   var runTime;
    
   //performance.now()相对Date.now()精度更高,并且不会受系统程序堵塞的影响。
   //API:https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now
   var startTime = performance.now(); //获取当前时间 
   
   //普通任务-对比测试
   setInterval(function(){
       runTime = performance.now();
       ++count;    
       console.log("普通任务",count + ' --- 普通任务延时:' + (runTime - (startTime + 1000))+' 毫秒');
       startTime = performance.now();
   }, 1000);
   
   //耗时任务
   setInterval(function(){
       let i = 0;
       while(i++ < 100000000);
   }, 0);
   
   // worker 解决方案
   let worker = new Worker('worker.js');
   </script>
   </body>
   </html>
   // worker.js
   var count = 0;
   var runTime;
   var startTime = performance.now();
   setInterval(function(){
       runTime = performance.now();
       ++count;    
       console.log("worker任务",count + ' --- 延时:' + (runTime - (startTime + 1000))+' 毫秒');
       startTime = performance.now();
   }, 1000);
(1)**同源限制**
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
(2)**DOM 限制**
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用`document``window``parent`这些对象。但是,Worker 线程可以`navigator`对象和`location`对象。
(3)**通信联系**
Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
(4)**脚本限制**
Worker 线程不能执行`alert()`方法和`confirm()`方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
(5)**文件限制**
Worker 线程无法读取本地文件,即不能打开本机的文件系统(`file://`),它所加载的脚本,必须来自网络。

前端面试-高频算法题

1. 数组排序,快排,归并排序

var sortArray = function(nums) {
    // 快速排序
    if(nums.length<=1) {
        return nums
    }
    let pivotIndex = Math.floor(nums.length/2)
    let pivot = nums.splice(pivotIndex,1)[0]
    let left=[],right=[]
    for(let i=0;i<nums.length;i++) {
        if(nums[i]<pivot) {
            left.push(nums[i])
        } else {
            right.push(nums[i])
        }
    }
    return sortArray(left).concat(pivot,sortArray(right))
    
    // 归并排序
    const mergeSort = (arr) => {
        // 序列长度为1时退出
        if (arr.length <= 1 ) {
            return arr
        }
        // 将序列分为两个子序列,这一块用到“分治法”中的“分割”
        const middle = Math.floor(arr.length/2)
        const left = arr.slice(0, middle)
        const right = arr.slice(middle)
        // 递归,这一块用到“分治法”中的“集成(合并)”
        return merge(mergeSort(left), mergeSort(right))
    }
    const merge = (left, right) => {
        const result = []
        // 两个子序列进行比较,从小到大放入新的序列result中
        while(left.length > 0 && right.length > 0) {
            // 将较小的放入result,并改变left或者right的长度,灵活使用shift方法
            if (left[0] < right[0]) {
                result.push(left.shift())
            } else {
                result.push(right.shift())
            }
        }
        // 先将小的元素放入result中,直到left或者right为空,剩余的一个数组肯定是大于result的有序序列,所以直接通过concat进行合并返回
        return result.concat(left, right)
    }
    return mergeSort(nums)
};

2. 版本号排序

let arr = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']
arr.sort((a, b) => {
    const arr1 = a.split(".")
    const arr2 = b.split(".")
    let i = 0
    while (true) {
        const s1 = arr1[i]
        const s2 = arr2[i]
        i++
        if (s1 === undefined || s2 === undefined) {
            return arr1.length - arr2.length
        }
        if (s1 === s2) continue
        return s1 -s2
    }
});
console.log(arr)

3. 括号生成

var generateParenthesis = function(n) {
    // 回朔法
    let ans = []
    let dfs = (str,l,r) => {
        if(l===n&&r===n) {
            ans.push(str)
            return
        }
        if(l<n) {
            dfs(str + '(',l+1,r)
        }
        if(r<l) {
            dfs(str + ')',l,r+1)
        }
    }
    dfs("",0,0)
    return ans
};

4. 最长递增子序列的个数

var findNumberOfLIS = function(nums) {
    let len = nums.length,maxLen = 0,ans = 0
    let dp = Array(len).fill(1)
    let cnt = Array(len).fill(1)
    for(let i=0;i<len;i++) {
        for(let j=0;j<i;j++) {
            if(nums[i]>nums[j]) {
                if(dp[j]+1>dp[i]) {
                    dp[i] = dp[j] + 1
                    cnt[i] = cnt[j] // 重置计数
                } else if(dp[j]+1===dp[i]) {
                    cnt[i] += cnt[j]
                }
            }
        }
        if(dp[i]>maxLen) {
            ans = cnt[i] //重置计数
            maxLen = dp[i]
        } else if(dp[i]===maxLen) {
            ans += cnt[i]
        }
    }
    return ans
};

5. 螺旋矩阵:给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

// 按层模拟 将矩阵看成若干层,首先输出最外层的元素,其次输出次外层的元素,直到输出最内层的元素
var spiralOrder = function(matrix) {
    let m = matrix.length
    if (m == 0) return []
    let n = matrix[0].length
    let ans = []
    let left = 0, right = n - 1, top = 0, bottom = m - 1;
    while (left <= right && top <= bottom) {
        for (let column = left; column <= right; column++) {
            ans.push(matrix[top][column]);
        }
        for (let row = top + 1; row <= bottom; row++) {
            ans.push(matrix[row][right]);
        }
        if (left < right && top < bottom) {
            for (let column = right - 1; column > left; column--) {
                ans.push(matrix[bottom][column]);
            }
            for (let row = bottom; row > top; row--) {
                ans.push(matrix[row][left]);
            }
        }
        [left, right, top, bottom] = [left + 1, right - 1, top + 1, bottom - 1];
    }
    return ans
};

6. 最长回文子串

// 中心扩展法
var longestPalindrome = function(s) {
    if (s.length<2){
        return s
    }
    let l=0;
    let r=0
    for (let i = 0; i < s.length; i++) {
        // 回文子串长度是奇数
        helper(i, i)
        // 回文子串长度是偶数
        helper(i, i + 1) 
    }
    function helper(m, n) {
        while (m >= 0 && n < s.length && s[m] == s[n]) {
            m--
            n++
        }
        // 注意此处m,n的值循环完后  是恰好不满足循环条件的时刻 如果此轮询得到回文串长度大于之前记录, 记录此轮循边界
        if (n - m - 1 > r-l-1) {
            r=n
            l=m
        }
    }
    return s.slice(l+1, r)
};

// 动态规划
const longestPalindrome = function(s) {
    let len = s.length
    let maxLen = 1
    let start = 0
    const dp = new Array(len).fill(0).map(() => new Array(len).fill(false))
    for(let l = 2 ; l <= len ; l ++){
        // 字串的长度,字符串长度从 2 开始
        for(let i = 0 ; i < len ; i ++) {
            // i 起始的位置
            // j 为终止位置
            let j = i + l - 1
            // 初始条件
            if((l == 2 || l == 3) && s[i] == s[j]) {
                dp[i][j] = true
            }
            // 递推公式
            if(s[i] === s[j] && dp[i + 1][j - 1] == true) {
                dp[i][j] = true
            }
            if(dp[i][j] && l > maxLen) {
                maxLen = l;
                start = i
            }
        }
    }
    return s.substr(start , maxLen)
}
let s = 'babad'
console.log(longestPalindrome(s))

7. TopK 数组中的第K个最大元素

利用快排的分区思想,每进行一次快速排序的分区操作,就能找到这次选中的基准值排序之后的正确位置

因为进行 partition操作之后,位于基准值之前的元素都要小于基准值,位于基准值之后的元素都要大于等于基准值

如果它的位置小于排序之后第 K 个最大元素的位置,我们就去它之后寻找第 K 个最大元素;

如果它的位置大于排序之后第 K 个最大元素的位置,我们就去它之前寻找第 K 个最大元素;

var findKthLargest = function(nums, k) {
    // 基于快排的分区思想
    const len = nums.length;
    const targetIndex = len - k;
    let left = 0,right = len - 1;
    const partition = (nums, start, end) => {
        const povit = nums[start];
        while (start < end) {
            while (start < end && nums[end] >= povit) {
                end--
            }
            nums[start] = nums[end];
            while (start < end && nums[start] < povit) {
                start++
            }
            nums[end] = nums[start]
        }
        nums[start] = povit
        return start
    } 
    while (left < right) {
        const index = partition(nums, left, right)
        if (index === targetIndex) {
            return nums[index]
        } else if (index < targetIndex) {
            left = index + 1
        } else {
            right = index - 1
        }
    }
    return nums[left]
};

8. 岛屿数量

// 深度优先搜索
var numIslands = function(grid) {
    const [row,col] = [grid.length,grid[0].length]
    let dfs = (grid,r,c) => {
        if(r<0||c<0||r>=row||c>=col||grid[r][c]==='0') {
            return 
        }
        grid[r][c] = '0'
        dfs(grid,r-1,c)
        dfs(grid,r+1,c)
        dfs(grid,r,c-1)
        dfs(grid,r,c+1)
    }
    let count = 0
    for(let i=0;i<row;i++) {
        for (let j=0;j<col;j++) {
            if(grid[i][j]==='1') {
                dfs(grid,i,j)
                count++
            }
        }
    }
    return count
};

9. 二分查找

// 1. 传统二分查找
var search1 = function(nums, target) {
    let left = 0, right = nums.length - 1;
    // 使用左闭右闭区间
    while (left <= right) {
        let mid = Math.floor((right + left)/2);
        if (nums[mid] > target) {
            right = mid - 1;  // 去左面闭区间寻找
        } else if (nums[mid] < target) {
            left = mid + 1;   // 去右面闭区间寻找
        } else {
            return mid;
        }
    }
    return -1;
};

var search2 = function(nums, target) {
    let left = 0, right = nums.length;
    // 使用左闭右开区间 [left, right)
    while (left < right) {
        let mid = Math.floor((right + left)/2);
        if (nums[mid] > target) {
            right = mid;  // 去左区间寻找
        } else if (nums[mid] < target) {
            left = mid + 1;   // 去右区间寻找
        } else {
            return mid;
        }
    }
    return -1;
};

// 2. 红蓝区间二分查找
const binarySearch1 = (nums,target) => {
    let left = -1,right = nums.length
    while(left+1!=right) {
        let mid = Math.floor((left+right)/2)
        // 下面括号里的判断条件表示左边蓝色区间的范围,return left就是小于target的最后一个位置,return right 就是大于等于target的第一个位置
        if(nums[mid]<target) {
            left = mid
        } else {
            right = mid
        }
    }
    return right
}

const binarySearch2 = (nums,target) => {
    let left = -1,right = nums.length
    // 下面括号里的判断条件表示左边蓝色区间的范围,return left就是小于等于target的最后一个位置,return right 就是大于target的第一个位置
    while(left+1!=right) {
        let mid = Math.floor((left+right)/2)
        if(nums[mid]<=target) {
            left = mid
        } else {
            right = mid
        }
    }
    return right
}

10.LRU缓存机制

var LRUCache = function(capacity) {
    this.map = new Map();
    this.capacity = capacity;
};

LRUCache.prototype.get = function(key) {
    if(this.map.has(key)){
        let value = this.map.get(key);
        this.map.delete(key); // 删除后,再 set ,相当于更新到 map 最后一位
        this.map.set(key, value);
        return value
    } else {
        return -1
    }
};

LRUCache.prototype.put = function(key, value) {
    // 如果已有,那就要更新,即要先删了再进行后面的 set
    if(this.map.has(key)){
        this.map.delete(key);
    }
    this.map.set(key, value);
    // put 后判断是否超载
    if(this.map.size > this.capacity){
        this.map.delete(this.map.keys().next().value);
    }

};

11.约瑟夫环问题:圆圈中最后剩下的数字

var lastRemaining = function(n, m) {
    let ans = 0
    for(let i=2;i<=n;i++) {
        ans = (ans+m)%i
    }
    return ans
};

12. 下一个排列

var nextPermutation = function(nums) {
    let i = nums.length - 2;
    // 从右往左遍历拿到第一个左边小于右边的 i,此时 i 右边的数组是从右往左递增的
    while (i >= 0 && nums[i] >= nums[i+1]){
        i--
    }
    if (i >= 0){
        let j = nums.length - 1;
        // 从右往左遍历拿到第一个大于nums[i]的数,因为之前nums[i]是第一个小于他右边的数,所以他的右边有大于他的数
        while (j >= 0 && nums[j] <= nums[i]){
            j--
        }
        // 交换两个数
        [nums[j], nums[i]] = [nums[i], nums[j]]
    }
    // 对 i 右边的数进行交换
    // 因为 i 右边的数原来是从右往左递增的,把一个较小的值交换过来之后,仍然维持单调递增特性
    // 此时头尾交换并向中间逼近就能获得 i 右边序列的最小值
    let l = i + 1;
    let r = nums.length - 1;
    while (l < r){
        [nums[l], nums[r]] = [nums[r], nums[l]]
        l++
        r--
    }
};

13. 最大正方形

https://leetcode-cn.com/problems/maximal-square/

14. 旋转图像

1.png

 // 方法三:水平线翻转+主对角线翻转
var rotate = function(matrix) {
    const n = matrix.length;
    // 水平翻转
    for (let i = 0; i < Math.floor(n / 2); i++) {
        for (let j = 0; j < n; j++) {
            [matrix[i][j], matrix[n - i - 1][j]] = [matrix[n - i - 1][j], matrix[i][j]];
        }
    }
    // 主对角线翻转
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < i; j++) {
            [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
        }
    }
};

15.无重复字符的最长子串

var lengthOfLongestSubstring = function(s) {
    let maxLen = 0
    let map = new Map()
    let queue = []
    for(let i=0;i<s.length;i++) {
        let ch = s[i]
        if(map.has(ch)) {
            while(true) {
                let c = queue.shift()
                if(c==ch) {
                    queue.push(ch)
                    break
                }
                map.delete(c)
            }
        } else {
            queue.push(ch)
            map.set(ch,1)
            maxLen = Math.max(queue.length,maxLen)
        }
    }
    return maxLen
};

16. K个一组翻转链表

function reverseKGroup( head ,  k ) {
    let pre = null
    let node = head
    let curr = head
    for(let i=0;i<k;i++) {
        if(node===null) {
            return head
        }
        node = node.next
    }
    for(let i=0;i<k;i++) {
        let next = curr.next
        curr.next = pre
        pre = curr
        curr = next
    }
    head.next = reverseKGroup(curr,k)
    return pre
}

17. 最小路径和

1.png

var minPathSum = function(grid) {
    let m = grid.length,n = grid[0].length
    let dp = Array(m).fill().map(item=>Array(n).fill(0))
    dp[0][0] = grid[0][0]
    for(let i=1;i<n;i++) {
        dp[0][i] = grid[0][i] + dp[0][i-1]
    }
    for(let i=1;i<m;i++) {
        dp[i][0] = grid[i][0] + dp[i-1][0]
    }
    for(let i=1;i<m;i++) {
        for(let j=1;j<n;j++) {
            dp[i][j] =Math.min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
        }
    }
    return dp[m-1][n-1]
};

18. Excel表列名称

1.png

var convertToTitle = function(columnNumber) {
    let ans = [];
    while (columnNumber > 0) {
        const a0 = (columnNumber - 1) % 26 + 1;
        ans.push(String.fromCharCode(a0 - 1 + 'A'.charCodeAt()));
        columnNumber = Math.floor((columnNumber - a0) / 26);
    }
    ans.reverse();
    return ans.join('');
};

前端面试-CSS布局相关

1. 0.5px的线

.test {
            transform: scale(0.5,0.5);
            border-top: 1px solid red;
        }

2. 水平垂直居中

/* 方式一: 定位 */
/* .parent {
    position: relative;
    height: 300px;
    background-color: bisque;
}
.child {
    position: absolute;
    height: 50px;
    background-color: red;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
} */

/* 方式二:定位 */
/* .parent {
    position: relative;
    height: 500px;
    width: 500px;
    background-color: antiquewhite;
}

.child {
    height: 50px;
    width: 50px;
    background-color: red;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
} */

/* 方式三:flex */
.parent {
    height: 500px;
    background-color: red;
    display: flex;
    justify-content:center;
    align-items:center;
}
.child {
    background-color: orange;
    height: 50px;
    width: 50px;
}

3. 两栏布局

/* 方式一:设置浮动 */
/*.outer {
    height: 500px;
}
.left {
    float: left;
    width: 200px;
    background: tomato;
}
.right {
    width: auto;
    background: orange;
    margin-left: 200px;
} */
/* 方式二:flex*/
.outer{
    display: flex;
    height: 100px;
}
.left {
    background: tomato;
    flex-basis: 200px;
}
.right {
    background-color:orange;
    flex: 1;
}

4. 三栏布局

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        /* 方式一:设置浮动:这种方式center必须放在最后 */
        /* .outer {
            height: 500px;
        }
        .left {
            float: left;
            width: 100px;
            background: tomato;
        }
        .right {
            float: right;
            width: 100px;
            background: orange;
        }
        .center {
            margin-left: 100px;
            margin-right: 100px;
            background-color: aqua;
        } */
        /* 方式二:flex*/
        /* .outer{
            display: flex;
            height: 100px;
        }
        .left {
            background: tomato;
            width: 200px;
        }
        .right {
            background-color:orange;
            width: 200px;
        }
        .center {
            background-color: aqua;
            flex: 1;
        } */
        /* 方式三: 定位 类似于浮动写法,两边设置浮动,中间设置margin*/
        .outer {
            background-color: gray;
            width: auto;
            height: 600px;
            position: relative;
        }
        .left {
            position: absolute;
            background-color: blue;
            width: 100px;
            height: 100%;
        }
        .right {
            background-color: red;
            width: 200px;
            right: 0;
            position: absolute;
            height: 100%;
        }
        .center {
            background-color: antiquewhite;
            margin-left: 100px;
            margin-right: 200px;
            height: 100%;
        }
    </style>
</head>
<body>
    <div class="outer">
        <div class="left"></div>
        <div class="right"></div>
        <div class="center"></div>
    </div>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        /* 方式四:圣杯布局*/
            /* 利用浮动和负边距来实现,父级元素设置左右的 padding,三列均设置向左浮动,中间一列放在最前面,宽度设置为父级元素的宽度,
            因此后面两列都被挤到了下一行,通过设置 margin 负值将其移动到上一行,再利用相对定位,定位到两边。*/
        .outer {
            height: 500px;
            padding-left: 100px;
            padding-right: 200px;
        }
        .center {
            float: left;
            width: 100%;
            height: 500px;
            background: lightgreen;
        }
        .left {
            position: relative;
            left: -100px;
            float: left;
            margin-left: -100%;
            width: 100px;
            height: 500px;
            background: tomato;
        }
        .right {
            position: relative;
            left: 200px;
            float: left;
            margin-left: -200px;
            width: 200px;
            height: 500px;
            background: gold;
        }
    </style>
</head>
<body>
    <div class="outer">
        <div class="center"></div>
        <div class="left"></div>
        <div class="right"></div>
    </div>
</body>
</html>

5. 实现三角形

/* div {
    width: 0;
    height: 0;
    border: 100px solid;
    border-color: orange blue red green;
} */
div {
    width: 0;
    height: 0;
    border-top: 50px solid red;
    border-right: 50px solid transparent;
    border-left: 50px solid transparent;
}

24. 实现扇形

div{
            border: 100px solid transparent;
            width: 0;
            height: 0;
            border-radius: 100px;
            border-top-color: red;
        }

6. 设置css 高度为宽度的两倍

<!-- 纯CSS的方法 -->
    <style>
        :root{
            --my-width:200px;
            --my-height:calc(var(--my-width) / 2)
        }
        div{
            background:yellow;
            width:var(--my-width);
            height:var(--my-height)
        }
    </style>
    <div></div>
    
    <!-- js方法 -->
    <div id="div" style="width: 100px; background: #F90;"></div>
    <script type="text/javascript">
        let div = document.getElementById("div");
        let width=parseInt(div.style.width) || div.offsetWidth;
        div.style.height=width*2 + "px";
    </script>

7. 实现一个box,鼠标移动的时候跟着鼠标一起移动,拖动盒子跟着鼠标走

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DRAG-拖拽</title>
</head>
<style>
    .box {
        box-sizing: border-box;
        width: 100px;
        height: 100px;
        background-color: lightblue;
        position: absolute;
        top: 0px;
        left: 0px;
        cursor: move;
    }
</style>
<body>
    <div class="box"></div>
</body>
<script>
    let box = document.querySelector(".box");
    box.onmousedown = down;
    
    function down(ev) {
        // 把鼠标的起始位置和盒子的起始位置存为自定义属性
        console.log(ev)
        this.pageX = ev.pageX;
        this.pageY = ev.pageY;
        this.left = this.offsetLeft;
        this.top = this.offsetTop;
        // 接下来再给盒子绑定MOVE和UP方法:只有鼠标按下的时候才去绑定移动和抬起
        // 谷歌的绑定方法:鼠标在document中操作,注意this的处理
        document.onmousemove = move.bind(this);
        document.onmouseup = up.bind(this);
    }
    function move(ev) {
        let curT = ev.pageY - this.pageY + this.top,
            curL = ev.pageX - this.pageX + this.left;
        // 边界处理
        // let minL = 0,
        //     maxL = document.documentElement.clientWidth - this.offsetWidth,
        //     minT = 0,
        //     maxT = document.documentElement.clientHeight - this.offsetHeight;
        // curL = curL < minL ? minL : (curL > maxL ? maxL : curL);
        // curT = curT < minT ? minT : (curT > maxT ? maxT : curT);

        this.style.left = curL + 'px';
        this.style.top = curT + 'px';
    }
    function up(ev) {
        // 鼠标抬起,把MOVE和UP都移除掉
        // 谷歌的解绑方法:
        document.onmousemove = null;
        document.onmouseup = null;
    }
</script>
</html>

8. 移动端布局,上导航栏固定,下菜单栏固定,中间自适应内容滑动

<div class="home">
    <div class="navigation">navigation</div>
    <div class="content"></div>
    <div class="bar">bar</div>
</div>

<style>
    .home {
        position: relative;
        height: 100vh;
    }
    .navigation {
        height: 100px;
        background-color: red;
        position: fixed;
        left: 0;
        right: 0;
        top: 0;
    }
    .content {
        overflow: auto;
        position: absolute;
        top: 100px;
        bottom: 100px;
        left: 0;
        right: 0;
    }
    .bar {
        height: 100px;
        background-color: lightblue;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
    }
</style>

9. css实现一行文字居中,多行文字左对齐

外层div文字居中,内层span设置行内块元素,文字居左

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    .demo {
        width: 100px;
        background-color: tomato;
        text-align: center;
        word-break: break-all;
    }
    .demo span {
        display: inline-block;
        text-align: left;
    }
</style>
<body>
    <div class="demo">
        <span>00000000000000000000000</span>
    </div>
    <script>

    </script>
</body>
</html>

9. animation 和 transition简单实现

<style>
  .root {
    width: 100%;
    height: 500px;
  }
  .box1 {
    width: 50px;
    height: 50px;
    background-color: orange;
    transition-property: margin-left;
    transition-duration: 2s;
    transition-delay: 0.5s;
    transition-timing-function: ease;
  }
  .box2 {
    width: 50px;
    height: 50px;
    background-color: pink;
    animation: myMove 2s ease 0.5s infinite normal forwards;
  }

  @keyframes myMove {
    from {
      margin-left: 0px;
    }
    to {
      margin-left: 300px;
    }
  }
</style>
<script>
  function clickButton() {
    const box = document.getElementsByClassName('box1')[0];
    // console.log(box.style);
    // console.log(getComputedStyle(box));
    box.style.setProperty('margin-left','300px');
  }
</script>
<body>
  <div class="root">
    <div class="box1"></div>
    <button onclick="clickButton()">点我移动橙色box</button>
    <div class="box2"></div>
  </div>
</body>