美文网首页
前端知识一

前端知识一

作者: 强某某 | 来源:发表于2021-04-13 17:29 被阅读0次

    防抖节流

    优化高频率事件 onscroll oninput resize onkeyup keydown.... 降低代码执行频率

    1.jpg
    • js动画/往页面里添加一些dom元素
    • style确定每个dom应该用什么样式规则
    • Layout布局,计算最终显示的位置和大小
    • Paint绘制dom,在不同的层上绘制
    • Composite渲染层合并

    用户scroll和resize行为会导致页面不断的重新渲染,如果在绑定的回调函数中大量操作dom也会出现页面卡顿

    优化方案:


    2.jpg 3.jpg

    函数节流

    节流就是保证一段时间内,核心代码只执行一次

    打个比方:水滴积攒到一定重量才会下落

    简易节流函数

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    
    <body>
        <button id="btn">点我aaaa</button>
    </body>
    
    </html>
    <script>
        // 节流,1s内的点击算一次
        let btn = document.getElementById('btn');
        //1.版本
        // function throttle(func,wait) {
        //     let previous=0;
        //     return function() {
        //         let now=Date.now();
        //         if (now-previous>wait) {
        //             func.apply(this,arguments);
        //             previous=now;
        //         }
        //     }
        // }
    
        //2.0版本
        function throttle(func, wait, option) {
            let args, context, previous = 0, timeout;
            let later = function () {
                previous = Date.now();
                func.apply(context, args);
                args=context=null;//避免内存泄漏
            }
            let throttled = function () {
                args = arguments;
                context = this;
                let now = Date.now();
                let remaning = wait - (now - previous);
                if (remaning <= 0) {
                    if (timeout) {
                        clearTimeout(timeout);
                        timeout = null;
                    }
                    func.apply(context, args);
                    previous = now;
                } else if (!timeout && option.trailing !== false) {
                    //最后一次的点击,要执行,就多了这么个功能的2.0
                    timeout = setTimeout(later, remaning);
                }
            }
            return throttled;
        }
        function logger() {
            console.log('logger');
        }
        // btn.addEventListener('click',throttle(logger,1000));
        btn.addEventListener('click', throttle(logger, 1000, { trailing: true }));
    </script>
    

    防抖

    防抖就是一段时间结束后,才触发一次事件,如果一段时间未结束再次触发事件,就会重新开始计算时间

    打个比方:电梯中,门快要关了,突然游刃准备上来,电梯并没有改变楼层,而是再次打开电梯门。电梯延迟了改变楼层的功能,但是优化了资源。

    简易防抖代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <button id="btn">点我aaaa</button>
    </body>
    </html>
    <script>
        //停止点击之后执行
         let btn = document.getElementById('btn');
         //1.0版本
        // function debounce(func,wait) {
        //     let timeout;
        //     return function() {
        //         clearTimeout(timeout);
        //         timeout=setTimeout(()=>{
        //             func.apply(this,arguments);
        //         },wait);
        //     }
        // }
        function debounce(func,wait,immediate) {
            let timeout;
            return function() {
                clearTimeout(timeout);
                //首次点击有效果
                if (immediate) {
                    let callNow=!timeout;
                    if (callNow) {
                        func.apply(this,arguments);
                    }
                }
                timeout=setTimeout(()=>{
                    func.apply(this,arguments);
                    clearTimeout(timeout);
                    timeout=null;
                },wait);
            }
        }
         function logger() {
            console.log('logger');
        }
        btn.addEventListener('click', debounce(logger, 1000,true));//第三个参数表示首次先触发一下
    </script>
    

    requestAnimationFrame

    编写动画循环的关键是要知道延迟时间多长合适,如果时间过长会导致动画补流畅,时间过短会造成过度的绘制。
    requestAnimationFrame采用系统时间间隔,保持最佳绘制效率。此方法是用来在页面重绘之前,
    通知浏览器调用一个指定的函数,被调用的频率是约每秒60次,在运行时浏览器会自动优化方法的调用.

    重点:这个函数的核心就是浏览器可以根据不同PC性能算出最佳绘制时间,以实现最佳显示效果

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <canvas id="canvas" width="500px" height="500px"> </canvas>
    </body>
    </html>
    <script>
        window.onload=function(){
            let oC=document.getElementById('canvas');
            let gd=oC.getContext('2d');
            let left=100;
            function next() {
                gd.clearRect(0,0,oC.with,oC.height);
                left+=5;
                gd.strokeRect(left,20,100,100);
                requestAnimationFrame(next);
            }
            requestAnimationFrame(next);
        }
    </script>
    

    柯里化

    函数柯里化,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数。
    其实质就是预先处理机制,核心就是利用闭包实现

    (function () {
        function myBind(context=window,...outerArg) {
            //此处的this是fn,因为是fn.myBind
            let _this=this;
            return function(...innerArg) {
                //此处就相当于fn.call
                // _this.call(context,...outerArg.concat(innerArg));
               //innerArg:就是自动传递的event对象
                _this.apply (context,outerArg.concat(innerArg));
            }
        }
        Function.prototype.myBind = myBind;
    })();
    
    
    
    let obj = {
        name: "OBJ"
    }
    function fn(...args) {
        console.log(this, args);
    }
    /**
    自定义实现了bind机制,而这个核心其实就是预先合并参数
    利用这个机制可以实现下面案例的题目
    */
    document.body.onclick=fn.myBind(obj,100,200);
    
    
    
    //ev浏览器会自动追加在最后面传递,bind其实返回就是匿名函数,和下面的等效
    //   document.body.onclick=fn.bind(obj,100,200);
    //其实bind函数内部就是这么实现的,可以避免立即执行
    // document.body.onclick = function (ev) {
    //     fn.call(obj, 100, 200, ev);
    // }
    

    假如需求是固定三层相加需求,但是每层参数不固定,可以如下实现

    function add(...A) {
        return function(...B) {
            return function(...C) {
                return eval([...A,...B,...C].join('+'))
            }
        }
    }
    //也可以(1,2,4)(3)(5)这种层数固定但是每层参数不固定
    add(1)(2)(3)
    

    下面实现核心逻辑

    /**请实现一个add函数,满足以下功能
     * add(1);  1
     * add(1)(2); 3
     * add(1)(2)(3)  6
     * add(1)(2)(3)(4) 10
     * add(1)(2,3)  6
     * add(1,2)(3) 6
     * add(1,2,3)  6
     */
    
    function currying(fn, length) {
        length = length || fn.length;
        return function (...args) {
            if (args.length >= length) {
                return fn(...args);
            }
            //传入null/undefined的时候将执行js全局对象浏览器中是window,其他环境是global
            return currying(fn.bind(null, ...args), length - args.length);
        }
    }
    // function $add(n1, n2, n3, n4) {
    //     return n1 + n2 + n3 + n4;
    // }
    // let add = currying($add, 4);
    // console.log(add(1)(2)(3)(4));
    // console.log(add(1, 2, 3, 4));
     //$add.bind(null,1).bind(null,2).bind(null,3)(4);
     //联系之前柯里化的案例
     //function any1(...innerArg){$add.call(null,...[1,...innerArg])}  这样一层层关联预处理了参数
     //function any2(...innerArg){any1.call(null,...[2,...innerArg])}
     //function any3(...innerArg){any2.call(null,...[3,...innerArg])}
     //any3(4)
     //any2.call(null,3,4)
     //any1.call(null,2,3,4)
     //$add.call(null,1,2,3,4)
    
    
    let add=currying((...arg)=>eval(arg.join('+')),5);
    console.log(add(1,2,3,4,5));
    

    总结:currying传递的第二参数就是实际真实计算是几个数据,一定要对应。
    而从currying内部逻辑可发现,其实是递归实现,内部也有闭包,而这种需求核心其实就是不论多少层每层多少参数都要可以计算,参考前面的柯里化思想,每层递归其实类似于call这种绑定关联。
    而在最终递归(预先处理)结束开始计算的时候,实际就是参数的合并逻辑。

    参考视频:https://www.bilibili.com/video/BV1aE411C7pt?p=27

    反柯里化

    从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。

    Function.prototype.uncurrying=function() {
        return str=>{
            // Object.prototype.toString.call('str')
            //this就是 Object.prototype.toString
            //Object.prototype.toString.call(参数)就是输出类型
            return this.call(str);
            //这只是简单案例,其实可以添加很多自己的逻辑
        }
    }
    let toString=Object.prototype.toString.uncurrying();
    //扩展了函数的功能
    console.log(toString('hello')); //[object String]
    
    //Object.prototype.toString本身就是一个函数,函数自然就有Function.prototype上面的属性
    

    总结:柯里化和反柯里化是一个思路,理解即可,不需要关心N多的具体实现,没有实际意义

    SourceMap

    说起sourceMap大家肯定都不陌生,随着前端工程化的演进,我们打包出来的代码都是混淆压缩过的,当源代码经过转换后,调试就成了一个问题。在浏览器中调试时,如何判断原始代码的位置?

    为了解决这个问题,google 提出了sourceMap 的想法,并在chorme上最先支持sourceMap的使用。sourceMap 由于包含许多信息,前期也经过多版的编码算法优化,最后在2011年探索出了Source Map Revision 3.0 ,这个版本也就是我们现在一直在使用的sourceMap版本。这一版本的mapping信息使用Base64 VLQ 编码,大大缩小了.map文件的体积。

    sourceMap可以帮我们直接定位到编译前代码的特定位置,接下来我们直接拿个sourceMap文件来看看它包含了一些什么信息:


    image.png

    上面可以看到,sourceMap其实就是就是一段维护了前后代码映射关系的json描述文件,包含了以下一些信息:

    • version:sourcemap版本(现在都是v3)
    • file:转换后的文件名。
    • sourceRoot:转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。
    • sources:转换前的文件。该项是一个数组,表示可能存在多个文件合并。
    • names:转换前的所有变量名和属性名。
    • mappings:记录位置信息的字符串。

    mappings 信息是关键,它使用Base64 VLQ 编码,包含了源代码与生成代码的位置映射信息。mappings的编码原理详解可见:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html,这里就不再详述。

    webpack中的sourceMap配置

    webpack 给出了多种sourceMap配置方式,相信很多人第一眼看到的时候和我一样,疑惑这些都有啥区别


    image.png

    其实不难发现这么多配置,这些就是source-map和eval、inline、cheap、module 的自由组合。所以我们来拆开看下每项配置。

    为了方便演示,这里的源代码只包含了一行代码

    console.log( hello world );

    最原始的只设置’source-map’配置,可以看到输出了两个文件,其中包含一个map文件


    image.png
    • eval
      每个模块用eval()包裹执行。
      • devtool: eval
        我们先看看单独的eval配置,这个配置相对于其他会特殊一点 。因为配置里没有sourceMap,实际上它也会生出map,只是它映射的是转换后的代码,而不是映射到原始代码。


        image.png
      • 2)devtool: eval-source-map
        所以eval-source-map就会带上源码的sourceMap,打包结果如下:


        image.png

    值得注意的是加了eval的配置生成的sourceMap会作为DataURI嵌入,不单独生成.map文件。

    对于eval的构建模式,我们可以看看官方的描述:可以看出官方是比较推荐开发场景下使用的,因为它能cache sourceMap,从而rebuild的速度会比较快。

    • inline
      inline配置想必大家肯定已经能猜到了,就是将map作为DataURI嵌入,不单独生成.map文件。
      devtool: inline-source-map构建出来的文件如下, 这个比较好理解,就不多说了


      image.png
    • cheap
      这是 “cheap(低开销)” 的 source map,因为它没有生成列映射(column mapping),只是映射行数 。
      为了方便演示,我们在代码加一行错误抛出:

    console.log('hello');
    throw new Error('this is test');
    

    可以看到错误信息只有行映射,但实际上开发时我们有行映射也基本足够了,所以开发场景下完全可以使用cheap 模式 ,来节省sourceMap的开销


    image.png
    • module
      Webpack会利用loader将所有非js模块转化为webpack可处理的js模块,而增加上面的cheap配置后也不会有loader模块之间对应的sourceMap。
      什么是模块之间的sourceMap呢?比如jsx文件会经历loader处理成js文件再混淆压缩, 如果没有loader之间的sourceMap,那么在debug的时候定义到上图中的压缩前的js处,而不能追踪到jsx中。
      所以为了映射到loader处理前的代码,我们一般也会加上module配置

    总结

    1、开发环境
    综上所述,考虑到我们在开发环境对sourceMap的要求是:快(eval),信息全(module),且由于此时代码未压缩,我们并不那么在意代码列信息(cheap),所以开发环境比较推荐配置:devtool: cheap-module-eval-source-map

    2、生产环境
    一般情况下,我们并不希望任何人都可以在浏览器直接看到我们未编译的源码,所以我们不应该直接提供sourceMap给浏览器。但我们又需要sourceMap来定位我们的错误信息, 这时我们可以设置hidden-source-map:
    一方面webpack会生成sourcemap文件以提供给错误收集工具比如sentry,另一方面又不会为 bundle 添加引用注释,以避免浏览器使用。
    当然如果没有这一类的错误处理工具,可以看看webpack推荐的其他配置:
    https://www.webpackjs.com/configuration/devtool/

    CSS sourceMap

    说起sourceMap我们第一反应通常是JavaScript的sourceMap,实际上现在css也可以使用sourceMap。因为sourceMap本质只是一个json,里面包含了源码的映射信息。所以其实只要了解sourcemap的编码规范,我们可以对任何我们想要的资源生成sourceMap,当然sourceMap 的支持也还是要取决于浏览器的支持。

    现在,对于css我们也有同样诉求,比如我现在打开调试器看到的样式配置没有任何源信息。如果想像js一样,知道这个css样式是在哪个文件需要怎么弄呢?

    image.png

    上面讲解的配置其实都是针对js的sourceMap,配置后webpack会自动帮我们生成各类js sourceMap。因为本质上webpack只处理js,对于webpack来说,css是否有sourceMap依赖于对css处理的loader是否有sourceMap输出,所以loader需要开启并传递sourceMap,这样最后生成的css才会带上sourceMap 。
    目前使用的css-loader,sass-loader都已经提供了生成sourceMap的能力,只需要我们加上配置即可。

    需要注意的是,这里如果要拿到sass编译前的源码信息,那么sourceMap一定要从sass-loader一直传递到css-loader,中间如有其他loader处理,也要透传sourceMap

    image.png

    可以看到,加了sourceMap 配置后,sourceMap会被内联在css代码里(这一层是css-loader处理的,与你是否使用min-extract-css-plugin抽出css无关)

    image.png

    加了css sourceMap后,我们可以很轻松的定位到sass编译前的源码路径了。

    image.png

    通过debug,打印出生成的css sourceMap,和js sourceMap对比并无他样:

    image.png

    利用css sourceMap 解决css url resolve的问题

    如果大家用了sass的话,很可能会遇到一个css url resolve的问题,在之前的一篇讲webpack 配置的文章里我也提到过:

    image.png

    实际上,利用css sourceMap这个问题便可以在不改变源码的情况下就可以完美解决。
    这里会增加一个loader去处理,loader处理流程主要分为二步:
    1、根据sourceMap的sourcesContent和url内容进行匹配,然后从sources定位到原有的css资源路径
    2、将传递给下个loader的url内容替换成绝对路径
    代码如下:

    module.exports = function (content, map) {
        const res = content.replace(/url((?: |")?((./|../)+([^ ")]*))( |")?)/g, (str, img, p2, imgPath) => {
            let index = -1;
            const {sourcesContent = [], sources = [], sourceRoot = []} = map || {};
            sourcesContent.some((item, i)=> {
                if (item.indexOf(img) !== -1) {
                    index = i;
                    return true;
                }
            });
            if (index !== -1) {
                const dir = path.dirname(sources[index]); // 获取文件所在目录
                str = str.replace(img, `~${path.join(dir, img)}`);
            }
            return str;
        });
        this.callback(null, res, map);
        return;
    }
    

    因为依赖sass-loader 处理之后的sourceMap, 所以@tencent/im-resolve-url-loader应配置在sass-loader 前面,配置如下:

    image.png

    说明:sourcemap部分内容,完全复制前端Q公众号文章:hSourceMap知多少:介绍与实践

    SVG矢量图操作

    概述

    svg是有一种基于xml语法的图像格式,全称是可缩放矢量图。其他图像格式都是基于像素处理的,svg则是属于对图像的形状描述,所以它本质上是文本文件,体积比较小,且不管放多少倍都不会失真。

    SVG文件可以直接插入网页,成为DOM的一部分,然后js和css进行操作。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <svg id="mysvg" xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 800 600"  preserverAspectRatio="xMidYMid meet">
        <circle id="mycircle" cx="400" cy="300" r="50" ></circle>
        </svg>
    </body>
    </html>
    

    上面是svg代码直接插入网页的例子。

    SVG代码也可以写在一个独立文件中,然后使用img/object/iframe/embed等标签插入网页。

    <img src="circle.svg>
    <object id="object" data="circle.svg" type="image/svg+xml"></object>
    <embed id="embed"  src="icon.svg" type="image/svg+xml">
    <iframe id="iframe"  src="icon.svg">
    

    css也可以使用SVG文件

    .logo{
        background:url(icon.svg);
    }
    

    SVG文件还可以转为BASE64编码,然后作为DataURL写入网页

    <img src="data:image/svg+xml;base64,[data]"/>
    

    语法

    • <svg>标签
      svg代码都放在顶层标签<svg>之中,下面是一个例子
        <svg width="100%" height="100%">
            <circle id="mycircle" cx="50" cy="50" r="50" fill="red"></circle>
        </svg>
    

    fill是填充色,width属性和height属性,制定了SVG图像再HTML元素中所占据的宽度和高度。除了相对单位,也可以采用绝对单位(px)。如果不指定这两个元素,SVG图像默认大小是300px(宽)*150px(高)

    如果只想展示SVG图像的一部分,就要指定viewBox属性

    <svg width="100" height="100"  viewBox="50 50 50 50">
        <circle id="mycircle" cx="50" cy="50" r="50"/>
    </svg>
    

    viewBox属性的的值有四个数字,分别是左上角的横坐标和纵坐标,视口的高度和宽度。上面的代码中SVG图像是100px*100px,viewBox属性指定视口从(50,50)这个点开始。所以,实际看到的是右下角的四分之一圆。

    注意:视口必须适配所在的空间。上面代码中,视口的大小是50 50,由于SVG图像的大小是100*100;所以视口会放大去适配SVG图像的大小,即放大了四倍。


    1.png
    • <circle>标签
    <svg width="300" height="180">
            <circle  cx="30" cy="50" r="25" ></circle>
            <circle  cx="90" cy="50" r="25"  class="red"></circle>
            <circle  cx="150" cy="50" r="25"  class="fancy"></circle>
    </svg>
    

    上面的代码定义了三个圆,<circle>标签的cx,cy,r属性分别是横坐标,纵坐标和半径,单位为像素。坐标都是相对于<svg>画布的左上角原点。

    class属性用来指定对应的css类

    <style>
    .red{
        /* 填充色 */
        fill: red;
    }
    .fancy{
        fill: none;
        /* 边框 */
        stroke: black;
        /* 边框宽度  3像素,注意是pt*/
        stroke-width: 3pt;
    }
    </style>
    
    • <line>标签
      用来绘制直线
    <svg width="300" height="180">
            <line x1="0" y1="0" x2="200" y2="0" style="stroke: rgb(0, 0, 0);stroke-width:5"></line>
        </svg>
    

    上面代码中,x1 y1代表线段的起点的横坐标和纵坐标;x2 y2属性,表示线段终点的横坐标和纵坐标;style属性表示线段的样式

    <style>
        .line {
            stroke: rgb(0, 0, 0);
            stroke-width: 5;
            transition: all 1s;
    
            /* transform: rotate(45deg); */
            /* 旋转中心点 */
            transform-origin: center; 
            animation: xuanzhuan  2s linear infinite;
        }
        @keyframes xuanzhuan {
            from{
                transform: rotate(0deg);
            }
            to{
                transform: rotate(360deg);
            }
        }
        .line:hover{
            stroke: yellow;
        }
    </style>
    
     <svg width="400" height="400">
            <line x1="50" y1="50" x2="350" y2="350" class="line"></line>
        </svg>
    

    鼠标移动到线上面 1s之内颜色变成黄色,然后自定义了一个动画,xuanzhuan,2s一次,无线循环从0度转到360度,效果就是以直线中间为圆心开始旋转

    • <polyline>标签
      用于绘制一根折线
     <svg width="300" height="180">
           <polyline points="3,3 30,28 3,53" fill="none" stroke="black"></polyline>
        </svg>
    

    <polyline>的points属性指定了每个端点的坐标,横坐标和纵坐标之间与逗号隔开,点与点之间用空格分割。

    • <rect>标签
      用于绘制矩形
    <svg width="400" height="400">
        <rect x="50" y="50" width="250" height="250" fill="red"></rect>
    </svg>
    

    <rect>的x和y属性,指定了矩形左上角端点的横坐标和纵坐标,width和height属性指定了矩形的宽度和高度(px)

    • <ellipse>标签
      用于绘制椭圆
    <svg width="400" height="400">
            <ellipse cx="60" cy="60" ry="40" rx="20" stroke="black" stroke-width="5"></ellipse>
    </svg>
    

    <ellipse>的cx cy属性指定了椭圆圆心的横坐标和纵坐标;rx ry属性,指定了椭圆的横向轴和纵向轴的半径(px)

    • <polygon>标签
      用于绘制多边形
    <svg width="400" height="400">
        <polygon fill="green" stroke="orange" stroke-width="1" points="0,0 100,0 100,100 0,100 0,0"></polygon>
    </svg>
    

    如上其实就是绘制了一个绿色填充,橙色边框的正方形

    <polygon>的points属性指定了每个端点的坐标,横坐标和纵坐标之间与逗号分隔,点与点之间用空格分割

    • <path>标签
      用于绘制路径
     <svg width="400" height="400">
           <path
            d="
            M 18,3
            L 46,3
            L 46,40
            L 61,40
            L 32,68
            L 3,40
            L 18,40
            Z
            "
           ></path>
        </svg>
    

    <path>的d属性表示绘制顺序,它的值是一个长字符串,每个字母表示一个绘制动作,后面跟着坐标。此时是箭头的形状

    • M:移动到(moveto)
    • L:画直线到(lineto)
    • Z:闭合路径
    2.png
    • <text>标签
      用于绘制文本
    <svg width="400" height="400">
            <text x="50" y="25">Hello World</text>
        </svg>
    

    x y属性,表示文本区块基线起点的横坐标和纵坐标,文字的样式可以用class或style属性.字体的颜色用fill填充而不是color

    • <use>标签
      用于复制一个形状
    <svg width="400" height="400">
        <text id="text" x="50" y="25">Hello World</text>
        <use href="#text" x="10" y="0" fill="blue"></use>
    </svg>
    

    <use>的href属性指定所要复制的节点,x和y属性是左上角的坐标。另外,还可以指定width和height坐标

    • <g>标签
      用于把多个形状组成一个组,方便复用
    <svg width="400" height="400">
            <g id="myg">
                <text id="text" x="50" y="25">Hello World</text>
                <circle cx="50" cy="50"></circle>
            </g>
    
            <use href="#myg" x="100" y="0" fill="blue"></use>
            <use href="#myg" x="200" y="0" fill="white" stroke="blue"></use>
        </svg>
    
    • <defs>标签
      用于自定义形状,它内部的代码不会显示,仅供引用
     <svg width="400" height="400">
            <defs>
                <g id="myg">
                    <text id="text" x="50" y="25">Hello World</text>
                    <circle cx="50" cy="50"></circle>
                </g>
            </defs>
            <use href="#myg" x="200" y="0" fill="white" stroke="blue"></use>
        </svg>
    

    这种方式无法再html页面直接通过css操作样式,但是可以通过js

    <svg xmlns="http://www.w3.org/2000/svg"
        xmlns:xlink="http://www.w3.org/1999/xlink">
        <style type="text/css" >
          <![CDATA[
            .water {
                    stroke-dasharray: 10, 5;
                }
    
                .runing {
                    stroke-dashoffset: -2000;
                    animation: run 4s linear infinite;
                }
    
                @keyframes run {
                    from {
                        stroke-dasharray: 10, 5;
                    }
    
                    to {
                        stroke-dasharray: 11, 5;
                    }
                }
    
          ]]>
        </style>
         <defs>
                <marker id="arrow-r" markerUnits="strokeWidth" markerWidth="12" markerHeight="12" viewBox="0 0 12 12"
                    refX="6" refY="6" orient="auto">
                    <path d="M2,2 L10,6 L2,10 L6,6 L2,2" style="fill: pink;" />
                </marker>
            </defs>
         <defs>
                <marker id="arrow-l" markerUnits="strokeWidth" markerWidth="12" markerHeight="12" viewBox="0 0 12 12"
                    refX="6" refY="6" orient="auto">
                    <path d="M10,2 L2,6 L10,10 L6,6 L10,2" style="fill: blue;" />
                </marker>
            </defs>
        <polyline class="runing"  points="10,10 410,10 " marker-start="url(#arrow-l)"
                marker-end="url(#arrow-r)" style="stroke:green;stroke-width:2" stroke-linecap="round" />
    </svg>
    

    defs还可以添加css样式

    • <pattern>标签
      用于自定义一个形状,该形状可以被引用来平铺一个区域
    <svg width="400" height="400">
            <defs>
                <pattern id="dots" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse">
                    <circle fill="#bee9e8" cx="50" cy="50" r="35"></circle>
                </pattern>
            </defs>
            <rect x="0" y="0" width="100%" height="100%" fill="url(#dots)"></rect>
        </svg>
    

    上面代码,将一个原形定义为dots模式。patternUnits="userSpaceOnUse"表示<pattern>的宽度和长度是实际的像素值。然后,指定这个模式去填充下面的矩形

    3.png
    • <image>标签
      用于插入图片文件
    <svg viewBox="0 0 100 100" width="100" height="100">
        <image xlink:href="path/to/image.jpg" width="50%" height="50%">
    </svg>
    

    xlink:href属性表示图像的来源

    • <animate>标签
      用于产生动画效果
    <svg width="400" height="400">
            <rect x="0" y="0" width="100" height="100" fill="#feac5e">
                <animate attributeName='x' from='0'  to="500" dur="2s" repeatCount="indefinite"></animate>
            </rect>
        </svg>
    

    上面代码中,矩形会不断移动,产生动画效果,循环往复。

    • attributeName:发生动画效果的属性名
    • from: 单次动画的初始值
    • to: 单次动画的结束值
    • dur: 单次动画的持续时间
    • repeatCount:动画的循环模式

    可以在多个属性上定义动画

    <animate attributeName='x' from='0'  to="500" dur="2s" repeatCount="indefinite"></animate>
    <animate attributeName='width'  to="500" dur="2s" repeatCount="indefinite"></animate>
    
    • <animateTransform>标签
      <animate>标签对css的transform属性不起作用,如果需要变形,就要使用<animateTransform>标签
    <svg width="400" height="400">
            <rect x="0" y="0" width="100" height="100" fill="#feac5e">
                <animateTransform attributeName='transform' type='rotate' 
                begin='0s' dur='10s' 
                from='0 200 200'  to="360 400 400"  repeatCount="indefinite"></animateTransform>
            </rect>
        </svg>
    

    上面代码中<animateTransform>的效果为旋转(rotate),这时from和to属性值有三个数字,第一个数字是角度值,第二个值和第三个值是旋转中心的坐标。
    from='0 200 200'表示开始的时候,角度为0,围绕(200,200)开始旋转,to='360 400 400'表示结束时,角度为360,围绕(400,400)旋转。

    JS操作

    • DOM操作
      如果SVG代码直接写在html网页之中,它就成为网页DOM的一部分,可以直接用DOM操作
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <style>
    </style>
    
    <body>
        <!-- <svg width="500" height="500">
            <rect x="0" y="0" width="100" height="100" fill="#feac5e">
                <animateTransform attributeName='transform' type='rotate' 
                begin='0s' dur='10s' 
                from='0 200 200'  to="360 400 400"  repeatCount="indefinite"></animateTransform>
            </rect>
        </svg> -->
    
        <svg width="400" height="400">
            <rect x="50" y="50" width="250" height="250" fill="red"></rect>
        </svg>
    
        <button>点击放大</button>
    </body>
    
    </html>
    <script>
       const btn= document.querySelector('button');
       btn.onclick=function(){
           let svgRect=document.querySelector('rect');
           //直接打印svgRect得到的dom字符串,如下形式才能展开内部属性
        //    console.log([svgRect]); 可发现很多属性都在attribute内部
           svgRect.setAttribute("width",350);
           svgRect.setAttribute("height","350");
        //    svgRect.style.fill="blue";  等效
           svgRect.setAttribute("fill","blue");
        //    svgRect.setAttribute("x",200);
    
           //通过间隔函数:实现动画
           let sudu=2;
           let weizhi=50;
           setInterval(()=>{
               weizhi+=sudu;
            svgRect.setAttribute("x",weizhi);
           },10);
    
       }
    </script>
    
    • 获取SVG DOM
      使用<object> <embed>标签插入SVG文件,可以获取SVG DOM
    const svgObject = document.getElementById('object').contentDocument;
    const svgIframe = document.getElementById('iframe').contentDocument;
    const svgEmbed = document.getElementById('embed').getSVGDocument();
    

    注意:如果使用<img>标签插入SVG文件,就无法获取SVG DOM。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <style>
    </style>
    
    <body>
        <iframe src="a.svg" width="450" height="200" scrolling="none" style="border: none;" id="iframe"></iframe>
        <button>点击放大</button>
    </body>
    
    </html>
    <script>
        // const svgObject = document.getElementById('object').contentDocument;
        // const svgEmbed = document.getElementById('embed').getSVGDocument();
        const svgIframe = document.getElementById('iframe');
        // console.log([svgIframe]);
        svgIframe.onload=function(){
            //必须这样,否则js执行很快,但是dom可能还没加载完毕,导致打印的contentDocument可能出错
           let doc= svgIframe.contentDocument;
            //注意:针对继续的子查询的时候,可能很多时候没有id等,例如path一般都有pid,可以通过css的属性选择器获取"[p-id='9655']"
           console.log(doc);
        }
    </script>
    

    案例一

    <svg xmlns="http://www.w3.org/2000/svg"
        xmlns:xlink="http://www.w3.org/1999/xlink">
        <style type="text/css" >
          <![CDATA[
          
            <!--自定义css变量-->
            :root{
                --from-stroke-dasharray:10 5;
                --to-stroke-dasharray:11, 5;
            }
            .water {
                    stroke-dasharray: 10, 5;
                }
    
                .runing {
                    stroke-dashoffset: -2000;
                    animation: run 4s linear infinite;
                }
    
                @keyframes run {
                    from {
                        stroke-dasharray: var(--from-stroke-dasharray);
                    }
    
                    to {
                        stroke-dasharray: var(--to-stroke-dasharray);
                    }
                }
    
          ]]>
        </style>
         <defs>
                <marker id="arrow-r" markerUnits="strokeWidth" markerWidth="12" markerHeight="12" viewBox="0 0 12 12"
                    refX="6" refY="6" orient="auto">
                    <path d="M2,2 L10,6 L2,10 L6,6 L2,2" style="fill: pink;" />
                </marker>
            </defs>
         <defs>
                <marker id="arrow-l" markerUnits="strokeWidth" markerWidth="12" markerHeight="12" viewBox="0 0 12 12"
                    refX="6" refY="6" orient="auto">
                    <path d="M10,2 L2,6 L10,10 L6,6 L10,2" style="fill: blue;" />
                </marker>
            </defs>
        <polyline class="runing"  points="10,10 410,10 " marker-start="url(#arrow-l)"
                marker-end="url(#arrow-r)" style="stroke:green;stroke-width:2" stroke-linecap="round" />
    </svg>
    
    <!DOCTYPE HTML>
    <html>
    
    <head>
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
    </head>
    
    <body>
        <div id="contanid1">
            <object type="image/svg+xml" data="a.svg" width="800" height="50"></object>
        </div>
        <div id="contanid2">
            <object type="image/svg+xml" data="a.svg" width="800" height="50"></object>
        </div>
    
        <button id="btn1">虚线一切换流动</button>
        <button id="btn2">虚线二切换流动</button>
        <button id="btn3">虚线二左边箭头显示切换</button>
    </body>
    
    </html>
    <script>
        window.onload = function () {
            const c1 = document.getElementById('contanid1');
            const c2 = document.getElementById('contanid2');
            const c1doc = c1.children[0].contentDocument;
            const c1fline = c1doc.children[0].children[3];
            c1doc.children[0].children[1].children[0].children[0].style.fill = 'yellow'
    
    
            const c2doc = c2.children[0].contentDocument;
            // console.log(111,[c2doc]); 注意这种打印方式
            const c2fline = c2doc.children[0].children[3];
            c2doc.children[0].children[1].children[0].children[0].style.fill = 'blue'
    
            let btn1 = document.getElementById('btn1');
            let btn2 = document.getElementById('btn2');
            let btn3 = document.getElementById('btn3');
            // console.log(1111, [c1fline]);
            btn1.onclick = function () {
    
                c1fline.attributes.class.value === 'water' ? c1fline.setAttribute('class', 'runing') : c1fline.setAttribute('class', 'water');
            }
            btn2.onclick = function () {
                c2fline.attributes.class.value === 'water' ? c2fline.setAttribute('class', 'water runing') : c2fline.setAttribute('class', 'water');
            }
            btn3.onclick = function () {
                c2doc.children[0].children[2].children[0].children[0].style.display === 'none' ? c2doc.children[0].children[2].children[0].children[0].style.display = '' : c2doc.children[0].children[2].children[0].children[0].style.display = 'none';
            }
    
    
    
    
            const rootStyles = getComputedStyle(c1doc.documentElement);
            //获取自定义属性值
            const value = rootStyles.getPropertyValue('--to-stroke-dasharray');
            //修改自定义属性值
            c1doc.documentElement.style.setProperty('--to-stroke-dasharray', '20 10');
            c1doc.documentElement.style.setProperty('--from-stroke-dasharray', '21 10');
           
        }
    </script>
    
    image.png

    案例二

    SVG绘制条形图

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title></title>
            <style type="text/css">
                .axis{
                    stroke: #999;
                    stroke-width: 2px;
                }
            </style>
        </head>
        <body>
            <!-- 
                1/获取数据
                2/创建SVG
                3/创建坐标
                4/绘制坐标文字
                5/依据数据绘制矩形(条形)
             -->
             <svg width="1000" height="700">
                 <g id="zuobiao">
                     <!-- x轴-->
                     <line class="axis" x1="50" y1="600" x2="950" y2="600" ></line>
                     <path d="M 925,590 L 950,600 L 925,610"></path>
                     <text x="920" y="630">时间</text>
                     <!-- y轴-->
                     <line class="axis" x1="100" y1="650" x2="100" y2="50"></line>
                     <path d="M 90,75 L 100,50 L 110,75"></path>
                     <text x="920" y="630">时间</text>
                     
                 </g>
                 <g id="xkedu">
                     <!-- <line class="axis" x1="170" y1="600" x2="170" y2="580"></line> -->
                     <text x="50" y="70">订单</text>
                </g>
                <g id="ykedu"></g>
                 <g id="barList">
                    
                 </g>
             </svg>
             <script type="text/javascript">
                var data = [{
                                data:"星期一",
                                order:"1000"
                            },
                            {   
                                data:"星期二",
                                order:"500"
                            },
                            {   
                                data:"星期三",
                                order:"600"
                            },
                            {   
                                data:"星期四",
                                order:"1100"
                            },
                            {   
                                data:"星期五",
                                order:"700"
                            },
                            {   
                                data:"星期六",
                                order:"1200"
                            },
                            {   
                                data:"星期日",
                                order:"1500"
                            }
                            ]
                    console.log(data)
                    
                    var xkedu = document.querySelector("#xkedu");
                    var ykedu = document.querySelector("#ykedu");
                    var barListDom = document.querySelector("#barList")
                    var jgLength = 700/data.length;
                    var yLength = 450/15;
                    for(var i= 1;i<=data.length;i++){
                        renderKedu(i)
                        console.log(i)
                    }
                    
                    for(var j=1;j<=15;j++){
                        ykedu.innerHTML = ykedu.innerHTML + `<line class="axis" x1="100" y1="${600-yLength*j}" x2="120" y2="${600-yLength*j}"></line>` +
                        `<text x="50" y="${600-yLength*j}">${100*(j)}</text>`
                    }
                    
                    function renderKedu(index){
                        var lineDom = document.createElement("line")
                        console.log(jgLength)
                        lineDom.className = "axis";
                        lineDom.setAttribute("x1",100+jgLength*index);
                        lineDom.setAttribute("y1","600");
                        lineDom.setAttribute("x2",100+jgLength*index);
                        lineDom.setAttribute("y2","580");
                        xkedu.innerHTML = xkedu.innerHTML + lineDom.outerHTML + `<text x="${75+jgLength*index}" y="620">${data[index-1].data}</text>`
                              
                        var color = `rgb(${parseInt(Math.random()*255)},${parseInt(Math.random()*255)},${parseInt(Math.random()*255)})`;
                        barListDom.innerHTML = barListDom.innerHTML + 
                        `<rect x="${75+jgLength*index}" y="${600-(yLength*(data[index-1].order/100))}" width="50" height="${yLength*(data[index-1].order/100)}" fill="${color}"></rect>`    
                    }   
             </script>
        </body>
    </html>
    
    image.png

    CSS相关

    自定义css属性

    即css变量,这样可以做到类似于less的效果,一改全改

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            :root {
                --div-color: red;
            }
    
            #d1 {
                width: 100px;
                height: 100px;
                background-color: var(--div-color);
            }
    
            #d2 {
                width: 200px;
                height: 200px;
                background-color: var(--div-color);
            }
        </style>
    </head>
    
    <body>
        <div id="d1">
            d1
        </div>
        <div id="d2">
            d2
        </div>
        <button>切换主题</button>
    </body>
    
    </html>
    <script>
        let btn = document.querySelector('button');
        btn.onclick = function () {
            //其实document.documentElement可以通过下面方式获取
            //let root=document.querySelector(":root");
            //其实都代表根的意思
            const rootStyles = getComputedStyle(document.documentElement);
            //获取自定义属性值
            const value = rootStyles.getPropertyValue('--div-color');
            //修改自定义属性值
            document.documentElement.style.setProperty('--div-color', 'green');
        }
    </script>
    

    注意:自定义css属性名称的时候,必须--开头,必须两个,这是规范

    H5属性的操作

    预定义属性

    h5提供了classList属性获取class的属性值,没有这个api之前,需要一点点获取attribute属性然后通过复杂匹配获取。

    <body>
        <div id='test' class="zq zq1 zq2"></div>
    </body>
    </html>
    <script>
        var test=document.querySelector("#test");
        console.log(test.classList)
        test.classList.add('zq3');
        console.log(test.classList);
        test.classList.remove('zq');
        console.log(test.classList);
        test.classList.toggle('zq');//如果存在就删除,不存在就添加
    </script>
    

    输出结果

    图一.png

    自定义属性

    h5之前,都是通过setattribute操作自定义属性

    <body>
        <div id='test'name='info'></div>
    </body>
    </html>
    <script>
        var test=document.querySelector("#test");
        test.setAttribute('name','info1');
        var h=test.getAttribute('name');
        console.log(h);//info1
    </script>
    

    H5案例:

    如果是data-开头的自定义属性,可以通过

    <body>
        <div id='test' data-name='info' data-type-info='hehe'></div>
    </body>
    </html>
    <script>
        var test=document.querySelector("#test");
        console.log(test.dataset.name);//info
        // data-type-info:获取data-开头的属性名的属性值,
        //后面的-的首字母大写
        console.log(test.dataset.typeInfo);//hehe
    </script>
    

    H5可编辑属性

    <body>
        <div id='test' contenteditable="true">可编辑属性</div>
    </body>
    
    可编辑属性.png

    H5语义化标签

    hgroup

    代表网页或者section的标题,当元素有多个层级时,该元素可以将h1到h6元素放在其内,譬如文章主标题和副标题的组合。


    使用细节:
    如果只有一个h1-h6标签不要使用hgroup
    如果有连续多个h1-h6标签就用hgroup
    如果有连续多个标题和其他文章数据,h1-h6标签就用hgroup标签包裹住,然后和其他文章元素一起放进header标签


    header

    代表网页或secction的页眉,通常包含h1-h6或hgroup


    使用细节:
    可以是"网页"或者任意"section"的头部部分
    没有个数限制
    如果hgroup或h1-h6自己就能工作很好,不要使用header


    nav

    代表页面的导航链接区域,用于定义页面的主要导航部分
    用在整个页面的主导航部分,不适合不要用nav元素

    section

    代表文档中的节或段,段可以是指一篇文章里按照字体的分段,节可以指一个页面的分组


    使用细节:
    section不是一般意义上的容器元素,如果想作为样式展示和脚本的便利,可以用div。
    article、nav、aside可以理解为特殊的section,
    所以如果可以用article、nav、aside就不要用section,没实际意义的就用div


    <section>
        <h1>section是啥?</h1>
        <article>
            <h2>关于section</h1>
            <p>section的介绍</p>
            <section>
                <h3>关于其他</h3>
                <p>关于其他section的介绍</p>
            </section>
        </article>
    </section>
    

    article

    article元素最容易跟section和div容易混淆,其实article代表一个在文档,页面或者网站中自成一体的内容


    使用细节:
    独立文章:用article
    单独的模块:用section
    没有语义的:用div


    <article>
        <h1>一篇文章</h1>
        <p>文章内容..</p>
        <footer>
            <p><small>版权:html5jscss网所属,作者:damu</small></p>
          </footer>
    </article>
    

    aside

    aside元素被包含在article元素中作为主要内容的附属信息部分,其中的内容可以是与当前文章有关的相关资料、标签、名次解释等.
    在article元素之外使用作为页面或站点全局的附属信息部分。最典型的是侧边栏,其中的内容可以是日志串连,其他组的导航,甚至广告,这些内容相关的页面。


    使用细节:
    aside在article内表示主要内容的附属信息,
    在article之外则可做侧边栏
    如果是广告,其他日志链接或者其他分类导航也可以用


    <article>
                <p>内容</p>
                <aside>
                    <h1>作者简介</h1>
                    <p>小北,前端一枚</p>
                </aside>
    </article>
    

    footer

    footer元素代表 网页 或 section 的页脚,通常含有该节的一些基本信息,譬如:作者,相关文档链接,版权资料。


    使用细节:
    footer使用注意:
    可以是 网页 或任意 section 的底部部分;
    没有个数限制,除了包裹的内容不一样,其他跟header类似。


    <footer>
         COPYRIGHT@damu
    </footer>
    

    Prop&&Attr

    checkbox

     <input type="checkbox" checked='checked'>
    

    说明:多选框中的checked属性除非不写,不然不论赋什么值,还是不写值,都代表选中。

    • attribute

    html的预定义属性和自定义属性

    input标签:针对的html
    所以如上:checked是input标签的attribute

    • property

    js原生对象的直接属性
    每一个预定义的attribute都会有一个property与之对应

    input节点:针对的是js
    所以如上:checked是input节点的property

    • 布尔值属性和非布尔值属性
    <body>
        <input type="checkbox" checked='checked' name='zq'>
    </body>
    </html>
    <script>
        //此时checked属性就是布尔值属性
        //name属性就是非布尔值属性
        //   布尔值属性:property的属性值是布尔值类型
        //   非布尔值属性:property的属性值是非布尔值类型
        //非布尔值属性:不论什么情况property和attribute都会同步
        //布尔值属性:1.改变property不会同步修改attribute
        //2.在没有动过property时,attribute会同步property,一旦动过property
        //attribute不会同步property
    var zq=document.querySelector('input[type=checkbox]');
    //修改attribute
    zq.setAttribute("name","zq1");
    //修改property
    zq.name='zq2';
    
    //浏览器只认property,用户操作的是property
    </script>
    
    
    • 属性使用attr还是prop的区分图


      prop&attr.png

    能使用attribute尽量使用attribute,性能高。

    基本案例:

    此时只有在第一次点击全选才有效,如果一旦第一次点击操作过property则全选无效,例如此时的水果三个全部手动选中,都是操作property,则在点击全选无效。如果在html全部给水果加上checked属性就相当于操作了property,此时即使第一次点击全选也无效。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>H5</title>
        <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    </head>
    <body>
        <input type="checkbox" >苹果
        <input type="checkbox" >栗子
        <input type="checkbox" >香蕉
        <input type="button" id="CheckAll" value="全选">
    </body>
    </html>
    <script>
      $(function(){
          $("#CheckAll").click(function(){
              $(":checkbox[type=checkbox]").attr("checked",true);
          })
      })
    </script>
    

    对比案例:

    checked属性是布尔值属性,如果使用操作property则一直有效

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>H5</title>
        <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    </head>
    <body>
        <input type="checkbox" >苹果
        <input type="checkbox" >栗子
        <input type="checkbox" >香蕉
        <input type="button" id="CheckAll" value="全选">
    </body>
    </html>
    <script>
      $(function(){
          $("#CheckAll").click(function(){
              $(":checkbox[type=checkbox]").prop("checked",true);
          })
      })
    </script>
    

    Web Workers

    • Worker:构造函数,加载分线程执行的js文件
    • Worker.prototype.onmessage: 用于接收另一个线程的回调函数
    • Worker.prototype.postMessage:向另一个线程发送消息
    • 缺点:worker内代码不能更新UI,不能跨域加载js,浏览器支持问题

    说明:必须在服务器环境下,不能本地file,加载的js不能跨域,使用webworker的代码不要使用
    箭头函数,let等新特性。

    案例

    html中的js
        var worker = new Worker('a.js');
        worker.postMessage(8);
        worker.onmessage = function (event) {
            console.log("接收的数据",event.data);
        }
    a.js
    // 斐波那契数列
    function fibonacci(n){
        return n<=2?1:fibonacci(n-1)+fibonacci(n-2);
    }
    var onmessage = function(event) {
        let res=fibonacci(event.data);
        postMessage(res);
    }
    说明:之所以webworker代码中不能更新UI,是因为this指向是[object DedicatedWorkerGlobalScope],
    即a.js中上下文不是window,没有UI更新相关的接口。
    

    浏览器渲染机制

    浏览器,再内核控制下相互配合以保持同步。它至少三个常驻的线程,JS引擎线程,GUI渲染线程,浏览器事件触发线程。

    1. js引擎是基于事件驱动单线程执行的
    2. 渲染线程负责渲染浏览器界面,但是GUI渲染线程与JS引擎互斥的,当JS引擎执行时GUI会被挂起;
      GUI的更新也会被保存在一个队列中,等到JS引擎空闲时候才有机会被执行。这就是JS阻塞页面加载
    3. 事件触发线程,当一个事件被触发时候该线程会把事件添加到任务队列的对尾,等待JS引擎的处理


      1.png

    浏览器内核

    • Trident内核:IE
    • Webkit内核:Chrome,Safari
    • Gecko内核:FireFox

    流程

    • 引擎一开始会从网络获取请求文档的内容,然后进行如下所示的基本流程:


      2.png

      呈现引擎将开始解析HTML文档,并将各标记逐个转化成内容树上的DOM节点。同时也会将解析外部CSS文件以及样式元素种的样式数据。
      HTML种这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树。

    呈现树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。

    呈现树构建完毕之后,进入"布局"处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。下一个阶段是绘制-呈现引擎会遍历呈现树,由用户界面后端层将每个节点绘制出来。

    • 当用户访问页面时,浏览器需要获取用户请求内容,这个过程主要涉及浏览器网络模块:
    1. 用户在地址栏输入域名,如baidu.com,DNS服务器根据输入的域名查找对应的IP,然后向该IP地址发起请求
    2. 浏览器获取并解析服务器的返回内容
    3. 浏览器加载HTML文件及文件内包含的外部引用文件以及图片,多媒体等资源

    这是一个渐进的过程。为了达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个HTML文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。


    3.png

    浏览器渲染过程

    image.png

    下半部分是上面的拆解;最后dispaly是显示;

    简化版的渲染过程

    image.png

    重排重绘

    重点:其中重排也就是回流;注意:修改颜色(样式变化)等,会引起重绘;而修改大小(几何变化)等会引起重排

    image.png

    从chrome性能监控上面可以发现,layout消耗了大量的CPU时间片。而Paint次之;所以能尽量避免重排就已经可以提供性能了;而下方会说明通过css3实现GPU加速避免重排重绘,提高性能,出让CPU,而使用GPU渲染。

    Composite 合成线程

    • Paint阶段就是绘制,但并不是把页面绘制到显示器上。绘制的本质是填充像素的过程,包括绘制文字、颜色、图像、边框、阴影等效果,也就是一个DOM元素的所有可视效果。

    • 而且,绘制过程一般是在多个图层上完成的,这些图层我们称之为渲染层。渲染层将保证页面中的元素以正确的顺序堆叠排列。

    • 也就是说,我们的页面不是2D平面的,而是三维立体的!

    • 前端所说的层叠上下文,其实正是因为页面的3D特性。

    • Composite阶段就是负责把所有图层,按照合理的顺序合并成一个图层。对于有元素位置重叠的页面,这个过程尤其重要。因为一旦图层的合并顺序出错,将会导致元素显示异常。

    • 这个模型类似PhotoShop的图层模型。在PhotoShop中,每个设计元素都是一个独立的图层,多个图层以前挡的顺序在Z轴空间上叠加,最终构成完整的设计图。

    合成层

    合成层是一种特殊的渲染层,这也是我们讨论的重点。

    Chrome浏览器提供了查看图层的工具:Layers,虽然很难用,但也可以看出其中的层级。
    先来看看淘宝的渲染层和合成层

    CSS3 跳过重排重绘,避开CPU,直接进入GPU硬件加速,执行动画,正是在合成层完成的。不影响其他渲染层。
    淘宝首页的banner提升到合成层:transform: translate3d()

    提升合成层的因素有很多,这里我们只演示 CSS3 transform

    代码案例~~~~~
    常用的还有will-change
    #target {
       will-change: transform, opacity;
    }
    

    渲染层自动提升合成层

    合成层并不是一定要通过手动开启的,有些原因也会自动提升合成层,其中,重叠原因是最常见的。


    image.png

    层爆炸与层压缩

    • 天下没有白吃的午餐!合成层虽好,但也是需要付出代价的!
    • 过多的合成层,意味着更复杂的图层管理,意味着对资源的消耗。
    • 这就是层爆炸。
    • 而且,GPU是显卡的核心部件,显卡的配置往往是比较低的,内存有限,很容易因为层爆炸而崩溃。
    • 当然,浏览器也想到了这一点,因此有了层压缩。
    • 当多个渲染层与同一个合成层重叠时,这些渲染会被压缩到一个图层上。
    • 但是,层压缩机制也不是万能的,也有很多原因不能进行层压缩,比如,不能进行会打破渲染顺序的压缩。

    css3直接使用GPU加速小结

    • 在实际开发中,我们常见一些CSS基础库会使用:box-sizing: border-box; 原因就是为了固定盒子大小,尽可能减少重排。
    • 尽量使用CSS3动画替代JS模拟动画,不仅可以利用硬件加速,减少重排,还不会占用JS主线程。而且浏览器会对CSS动画做优化。
    • transform: translateZ(0); 常用来欺骗浏览器,提升合成层,利用GPU硬件加速。
    • 性能优化并没有所谓的“银弹”,transform: translateZ(0) 不是,本文列出的优化建议也不是。
    • 抛开了对页面的具体分析,任何的性能优化都是站不住脚的,盲目的使用一些优化措施,结果可能会适得其反。
    • 因此切实的去分析页面的实际性能表现,不断的改进测试,才是正确的优化途径。

    什么是html的解析?
    解析html/css文档是指将文档转化成为有意义的结构,也就是可让代码理解和使用的结构。
    解析得到的结果通常是表示了文档结构的节点树,它被称为解析树或者语法树。

    解析的过程可以分为两个子过程:词法分析和语法分析

    • 词法分析
      词法分析是将输入内容分割成大量标记的过程。
      其实html本质上就是字符串,<html lang="en"><body></body></html>;
      需要在词法分析过程转变成一个个的词
    <html lang="en">
    <body>
        
    </body>
    </html>
    

    -语法分析
    应用语言的语法规则的过程。在认类语言中,相当于字典中的单词。
    根据(html or css等)语言的语法规则分析文档的结构,从而构建解析树。

    例子:2+3-1,分析过程:

    4.png
    这种方式被称为,移位规约解析器,因为输入在向右移动(设想有一个指针从输入内容的开头移动到结尾)(这之前是词法分析), 并且逐渐归约到语法规则上。
    • HTML解析器
      任务是将HTML标记解析成解析树。
      HTML的定义采用DTD的格式。此格式可用于定义SGML族的语言。它包括所有允许使用的元素及其属性和层次结构的定义。
      现在都是使用html5的规范来解析了,DTD不使用了。

    • DOM解析器
      作用是,输出"解析树"。是由DOM元素和属性节点构成的树结构。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
       <P></p> 
    </body>
    </html>
    

    首先浏览器从上到下依次解析文档构建的DOM树


    5.png
    • CSS例子:


      6.png
    7.png

    在上面的过程主要有两个阶段:

    1. 标记化
    2. 树的构建:

    标记化是词法分析的过程,将输入内容解析程多个标记。例如:HTML标记包含起始标记,结束标记,属性名称和属性值。不断的解析,直到加载网页结束。

    树的构建:在创建解析器的同时,也会创建Document对象。在树的构建阶段,以Document为根节点的DOM树也会不断进行修改,向其中添加各种元素。标记生成器发送的每个节点都会由树构建器进行处理。

    这些标记元素不仅会添加到DOM树种,还会添加到开放元素的堆栈种。此堆栈用于纠正嵌套错误和处理未关闭的标记。
    其算法也可以用状态机来描述,这些状态成为"插入模式"。

    • Dom树和渲染树
      每一个渲染对象都对应着DOM节点


      8.png

    创建渲染树之后,下一步就是布局(Layout)这个过程就是通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸,将其安置在浏览器窗口的正确位置。

    有些时候我们会在文档布局完成之后对DOM进行修改,这时候可能需要重新进行布局,也可以称其为回流(重绘)。
    根据需要,或是全局重绘,或是局部重绘,最终触发"重新渲染页面"。

    Object.create原理

    Bell.prototyp=Object.create(EventEmitter.prototype);
    
    //等效于下面三行
    function Temp() {}
    Temp.prototype=EventEmitter.prototype;
    Bell.prototype=new Temp();
    
    //等效于下面
    let bell=new Bell();
    bell.__proto__=EventEmitter.prototype;
    

    事件捕获和冒泡

    dom标准事件流的触发的先后顺序为:先捕获再冒泡。即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。

    1.png
    • addEventListener的第三个参数
    element.addEventListener(event, function, useCapture);
    

    第三个参数默认值是false,表示在事件冒泡阶段调用事件处理函数;如果参数为true,则表示在事件捕获阶段调用处理函数。

    • 事件冒泡
    <body>
      <div id="parent">
        父元素
        <div id="child">
          子元素
        </div>
      </div>
      <script type="text/javascript">
        var parent = document.getElementById("parent");
        var child = document.getElementById("child");
    
        document.body.addEventListener("click",function(e){
          console.log("click-body");
        },false);
    
        parent.addEventListener("click",function(e){
          console.log("click-parent");
        },false);
    
        child.addEventListener("click",function(e){
          console.log("click-child");
        },false);
      </script>
    </body>
    
    2.png

    事件触发顺序是由内到外的,这就是事件冒泡。如果点击子元素不想触发父元素的事件,可使用event.stopPropagation();方法:

    child.addEventListener("click",function(e){
      console.log("click-child");
      //该句是事件是否继续冒泡
       e.stopPropagation();
    },false);
    //false指的是在冒泡阶段执行,效果不同,配合起来使用可以精确控制
    
    • 事件捕获
    var parent = document.getElementById("parent");
    var child = document.getElementById("child");
    
    document.body.addEventListener("click",function(e){
      console.log("click-body");
      },false);
    
    parent.addEventListener("click",function(e){
      console.log("click-parent---事件传播");
    },false);
             
         //新增事件捕获事件代码
    parent.addEventListener("click",function(e){
      console.log("click-parent--事件捕获");
    },true);
    
    child.addEventListener("click",function(e){
      console.log("click-child");
    },false);
    
    3.png

    父元素通过事件捕获的方式注册了click事件,所以在事件捕获阶段就会触发,然后到了目标阶段,即事件源,之后进行事件冒泡,parent同时也用冒泡方式注册了click事件,所以这里会触发冒泡事件,最后到根节点(body)。这就是整个事件流程。

    相关文章

      网友评论

          本文标题:前端知识一

          本文链接:https://www.haomeiwen.com/subject/bfuclltx.html