场景题

241 阅读6分钟

在前端添加水印的方式

  • 使用canvas添加
    • 通过canvas绘制单个水印
    • 设置为某元素的背景图
    • 并且通过MutationObserver监听该元素,确保背景图不会被手动关掉。
            <canvas id = "canvas"></canvas>
            //利用canvas生成图片,然后在div的背景图中填充
            function setWaterWrapper(text) {
                //获取到canvas
                const canvas = document.getElementById('canvas');
                //给canvas添加宽高
                canvas.width = 200;
                canvas.height = 200;

                canvas.style.display = 'none';
                //设置canvas为二维
                const ctx = canvas.getContext('2d');
                //设置水印的字体、颜色、倾斜角度、内容
                ctx.font = '30px';
                ctx.fillStyle = 'rgba(200,44,99,0.7)'
                ctx.rotate(-0.3);
                ctx.fillText(text, canvas.width / 8, canvas.height / 2);
                //生成图片地址
                const img = canvas.toDataURL('image/png');
                //保存为样式
                const style = `background-image:url(${img})`;
                // idName.setAttribute('style',style)
                // let id = document.getElementById(idName);
                // id.style.backgroundImage = `url(${img})`
                return style;

            }
            let style = setWaterWrapper('水印水印')
            let box = document.getElementById('box');
            box.setAttribute('style',style)
            //一个被监听到修改时执行的回调函数
            function callback() {
                if (style!== box.style) {
                    box.setAttribute('style',style)
                }
            }
            //创建一个observer
            const observer = new MutationObserver(callback)
            //要进行监听的节点
            const targetNode = box;
            //要监听节点的什么内容。这里配置为属性
            const config = { attributes: true };
            //执行监听
            observer.observe(targetNode, config);
- 通过svg添加水印
- 给图片添加水印:使用canvas重新绘制图片,并输出图片url
- 通过上层覆盖一个dom元素,position:fixed

实现每隔一秒打印一次时间,时间格式为YYYY-MM-DD 00:00:00

  • 实现要点
    • 格式,如果是9月,date.getMonth()打印出来是9;
    • < date.getMonth().toString().padStart(0,'2')> 通过转换可以使打印出来是09
  • 代码实现
        function getTime() {
            //实现每隔一秒打印时间
            let date = new Date();
            // console.log(date.getDate(),date.getMonth()+1,date.getFullYear())
            let month = (date.getMonth() + 1).toString().padStart(2, '0')
            let year = date.getFullYear();
            let day = date.getDate().toString().padStart(2, '0');
            let hours = date.getHours().toString().padStart(2, '0');
            let min = date.getMinutes().toString().padStart(2, '0');
            let sec = date.getSeconds().toString().padStart(2, '0');
            console.log(year + '-' + month + '-' + day + ' ' + hours + ':' + min + ':' + sec)
        
        }

        //每隔一秒打印一次
        let timer = setInterval(() => {
            getTime()
        }, 1000);
        clearInterval(timer)

实现圣杯布局

  • 什么是圣杯布局
    • 有header,footer。中间部分container包含三个区域,left/center/right
    • 其中left和right宽度是固定的,center的宽度是自适应的
  • 使用float和margin负实现
    • 实现要点
    • 1、container中间的每一个元素都要设置为浮动元素
    • 2、footer设置取消浮动,避免上移
    • 3、container设置左右padding,为left和right预留出位置
    • 4、left的margin设置-100%,并且设置定位到指定位置
    • 5、right的margin设置负距离上移到container
<body>
    <div class="header"></div>
    <div class="container">
        <div class="center column"></div>
        <div class="left column"></div>
        <div class="right column"></div>
    </div>
    <div class="footer"></div>
</body>
<style>
    .header {
        background-color: aquamarine;
        height: 20px;
    }

    .footer {
        background-color: antiquewhite;
        height: 20px;
        /* 清除浮动 */
        clear: both;
    }

    .container {
        /* 留出左右两侧的空间 */
        padding-left: 200px;
        padding-right: 300px;
        background-color: rgb(173, 208, 239);
    }

    .container .column {
        height: 500px;
        float: left;
    }

    .left {
        /* 定位 */
        position: relative;
        left: -200px;
        /* 进入父元素的最左边 */
        margin-left: -100%;
        width: 200px;
        background-color: #bfa;
    }

    .right {
        margin-right: -300px;
        width: 300px;
        background-color: beige;
    }

    .center {
        width: 100%;
        background-color: blanchedalmond;
    }
</style>
  • 使用flex实现
    • 实现要点
    • container设置display:flex
    • 将center的flex设置为1,表示自适应占满
<style>
    .header{
        background-color: aliceblue;
    }
    .footer{
        background-color: aqua;
    }
    .container{
        height: 500px;
        display: flex;
    }
    .center{
        flex: 1;
        background-color: azure;
    }
    .left{
        width: 200px;
        background-color: bisque;
    }
    .right{
        width: 300px;
        background-color: aquamarine;
    }
</style>
  • 使用grid实现
    • 实现要点
    • container的display:grid
    • grid-template-areas: "head head head head" "left center center right" "foot foot foot foot";
    • 指定每哪一个是head,哪一个是foot、center、left、right
    <style>
        .header{
            background-color: aliceblue;
        }
        .footer{
            background-color: antiquewhite;
        }
        .left{
            grid-area: left;
            background-color: aqua;
        }
        .center{
            grid-area: center;
            background-color: beige;
        }
        .right{
            grid-area: right;
            background-color: bisque;
        }
        .container{
            display: grid;
            grid-template-areas:
             "head head head head"
             "left center center right"
             "foot foot foot foot";
        }
    </style>
  • 还用grid实现
    • 实现要点
    • 设置container的display:grid
    • grid-template-columns: 1fr 2fr 1fr;每一列所占的宽度
    <style>
        .header{
            background-color: aliceblue;
        }
        .footer{
            background-color: antiquewhite;
        }
        .left{
            background-color: aqua;
        }
        .center{
            background-color: beige;
        }
        .right{
            background-color: bisque;
        }
        .container{
            display: grid;
            grid-template-columns: 1fr 2fr 1fr;
           /* column-gap: 50px; */
        }
    </style>

使用proxy实现简单响应式对象

  • proxy
    • proxy可以代理一个对象
    • 不仅能够代理对象的读写操作,还可以代理增删
    • 使用Reflect中的方法操作对象。好处是不会报错而是返回操作成功与否的结果
  • 代码实现
        let person = {
            name:'lxy',
            pig:'hsh'
        }
        
        //使用p代理对对象person的操作
        let p = new Proxy(person,{
            get(target,propName){
                console.log(`读取${propName}`,target,propName)
                return Reflect.get(target,propName)
            },
            //修改或者追加属性都会调用。
            set(target,propName,value){
                console.log('修改或者增加',target,propName,value)
                Reflect.set(target,propName,value)
            },
            deleteProperty(target,propName){
                console.log('删掉了')
                return Reflect.deleteProperty(target,propName)
            }
        });

设计模式

单例设计模式

  • 什么是单例设计模式
    • 单例设计模式就是一个类仅能够创建一个实例,并且能够提供一个访问它的方法。
  • 有哪些单例设计模式?
    • 例如windows回收站、任务管理器。这种每次只能够打开一个的。
    • 比如每次点击登录按钮,无论点击多少次登录按钮,都只会弹出一次登录框
    • 像Vuex,jquery,lodash等第三方库都是单例模式,每次引用或者install都只会引用一次。
  • 实现简单的单例设计模式
    • 使用闭包实现
    //实现单例模式:通过闭包实现
    function Single(fn) {
        //闭包存储结果,下次调用时仍然保存在内存中,所以可以进行判断
        let res = null
        return function () {
            return res == null ? (res = fn()) : res;
        }
    }
    function createLogin() {
        let div = document.createElement('div');
        div.innerHTML = '这是一个登陆弹窗,只出现一次';
        div.style.display = 'none';
        document.body.append(div);
        return div;
    }
    let res = Single(createLogin);

    let btn = document.querySelector('button');
    btn.addEventListener('click', () => {
        let login = res();
        login.style.display = 'block'
    })
- 使用class实现:利用class中的静态类创建实例,如果实例有被创建,就返回已经创建的实例
    //使用静态class实现单例模式
    class SingleClass{
        constructor(name,creator,products){
            this.name = name;
            this.creator = creator;
            this.products = products;
        }
        //通过静态类创建唯一的实例
        static getInstance(name,creator,products){
            if(!this.instance){
                this.instance = new SingleClass(name,creator,products)
            }
            return this.instance
        }
    }
    //通过instance创建实例,整个类只能被创建一个实例。
    let apple = SingleClass.getInstance('apple','a','iphone');
    let copy = SingleClass.getInstance('applecopy','b','bphone');
    console.log(apple, copy)

工厂模式

  • 工厂模式
    • 是用于创建对象的一种常用的设计模式。只需要传入参数就可以不断的生产出实例对象。

代理模式

  • 当用户不方便直接访问一个对象时,提供一个代理对象代理对这个对象的全部属性。用户实际上是访问的代理对象
  • 缓存代理:为一些开销大的运算结果提供暂时性的存储。下次运算时,如果传递进来的参数和之前一致,可以直接返回存储的计算结果。
  • 虚拟代理:把一些开销很大的对象,延迟到真正需要的时候再去创建。

观察者模式,发布订阅模式

如何将数组结构转换为树结构?

  • 应用场景:在配合后端进行开发时,后端返回的数据是一个数组,需要按照列表中的父子关系转换为树结构进行展示
  • 从后端获取的数据列表:
   //从后端获取的数组内容,包含每条数据的id,包含每条数组父节点的id
        let data = [
            { id: 0, parentId: null, name: '生物' },
            { id: 1, parentId: 0, name: '动物' },
            { id: 2, parentId: 0, name: '植物' },
            { id: 3, parentId: 0, name: '微生物' },
            { id: 4, parentId: 1, name: '哺乳动物' },
            { id: 5, parentId: 1, name: '卵生动物' },
            { id: 6, parentId: 2, name: '种子植物' },
            { id: 7, parentId: 2, name: '蕨类植物' },
            { id: 8, parentId: 4, name: '大象' },
            { id: 9, parentId: 4, name: '海豚' },
            { id: 10, parentId: 4, name: '猩猩' },
            { id: 11, parentId: 5, name: '蟒蛇' },
            { id: 12, parentId: 5, name: '麻雀' }
        ]
  • 将数组转化为树结构的思路
    • 如要检查传入的数据是否为数组类型
    • 遍历每条数据,存到一个对象中,对象的key为id,value为每条数据
    • 遍历每条数据,取出对象中存在parentid的每条数据,
    • 如果存在parentid,就在相应的每条数据下添加children,为当前item
    • 如果没有parent,就在结果中添加当前的数据。

        //将数组转化为树结构
        function transTree(data) {
            let res = [];
            let map = {};
            //检查传进来的是不是数组
            if (!Array.isArray(data)) return []
            //存到map中,key是每条数据的id,val为每条数据的所有内容
            //map中保存的是值的引用。
            data.forEach(item => {
                map[item.id] = item;
            })
            // console.log(map)
            //循环遍历每一条数据,取出每条数据父节点的id
            //如果每个元素的父节点存在,就增加一个属性children并放进去
            data.forEach(item => {
                let parent = map[item.parentId]
                if (parent) {
                    (parent.children || (parent.children = [])).push(item);

                } else {
                    res.push(item);
                }
            })
            // console.log(res);
            return res
        }
        transTree(data)

面试题:如果给你一个项目你该怎么做?

  • 根据项目需求,确定项目类型
  • 评估项目中的风险和可行性
  • 根据自身和团队给出估时和排期,还有所需依赖的资源
  • 设计整体架构,分发模块到相关人员
  • 沟通和反馈,定期同步进度。
  • 测试,优化,总结。