前端知识一

防抖节流

优化高频率事件 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)。这就是整个事件流程。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容