前端知识一

防抖节流

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

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

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

优化方案:


前端知识一_第1张图片
2.jpg
前端知识一_第2张图片
3.jpg

函数节流

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

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

简易节流函数





    
    
    Document



    




防抖

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

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

简易防抖代码




    
    
    Document


    



requestAnimationFrame

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

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




    
    
    Document


     



柯里化

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

(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文件来看看它包含了一些什么信息:


前端知识一_第3张图片
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配置方式,相信很多人第一眼看到的时候和我一样,疑惑这些都有啥区别


前端知识一_第4张图片
image.png

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

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

console.log( hello world );

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


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


      前端知识一_第6张图片
      image.png
    • 2)devtool: eval-source-map
      所以eval-source-map就会带上源码的sourceMap,打包结果如下:


      前端知识一_第7张图片
      image.png

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

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

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


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

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

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


前端知识一_第9张图片
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样式是在哪个文件需要怎么弄呢?

前端知识一_第10张图片
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

前端知识一_第11张图片
image.png

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

前端知识一_第12张图片
image.png

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

image.png

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

前端知识一_第13张图片
image.png

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

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

前端知识一_第14张图片
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 前面,配置如下:

前端知识一_第15张图片
image.png

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

SVG矢量图操作

概述

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

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




    
    
    Document


    
    
    


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

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




    




案例一


    
     
            
                
            
        
     
            
                
            
        
    





    



    
前端知识一_第19张图片
image.png

案例二

SVG绘制条形图



    
        
        
        
    
    
        
         
             
                 
                 
                 
                 时间
                 
                 
                 
                 时间
                 
             
             
                 
                 订单
            
            
             
                
             
         
         
    

前端知识一_第20张图片
image.png

CSS相关

自定义css属性

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





    
    
    Document
    



    
d1
d2

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

H5属性的操作

预定义属性

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


    

输出结果

前端知识一_第21张图片
图一.png

自定义属性

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


    

H5案例:

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


    

H5可编辑属性


    
可编辑属性
前端知识一_第22张图片
可编辑属性.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是啥?

关于section

section的介绍

关于其他

关于其他section的介绍

article

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


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


一篇文章

文章内容..

版权:html5jscss网所属,作者:damu

aside

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


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


内容

footer

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


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


COPYRIGHT@damu

Prop&&Attr

checkbox

 

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

  • attribute

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

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

  • property

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

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

  • 布尔值属性和非布尔值属性

    




  • 属性使用attr还是prop的区分图


    前端知识一_第23张图片
    prop&attr.png

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

基本案例:

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




    
    
    
    H5
    


    苹果
    栗子
    香蕉
    



对比案例:

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




    
    
    
    H5
    


    苹果
    栗子
    香蕉
    



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引擎的处理


    前端知识一_第24张图片
    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文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。


前端知识一_第25张图片
3.png

浏览器渲染过程

前端知识一_第26张图片
image.png

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

简化版的渲染过程

前端知识一_第27张图片
image.png

重排重绘

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

前端知识一_第28张图片
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;
}

渲染层自动提升合成层

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


前端知识一_第29张图片
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 or css等)语言的语法规则分析文档的结构,从而构建解析树。

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

前端知识一_第30张图片
4.png

这种方式被称为, 移位规约解析器,因为输入在向右移动(设想有一个指针从输入内容的开头移动到结尾)(这之前是词法分析), 并且逐渐归约到语法规则上。

  • HTML解析器
    任务是将HTML标记解析成解析树。
    HTML的定义采用DTD的格式。此格式可用于定义SGML族的语言。它包括所有允许使用的元素及其属性和层次结构的定义。
    现在都是使用html5的规范来解析了,DTD不使用了。

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




    
    
    Document


   

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


前端知识一_第31张图片
5.png
  • CSS例子:


    前端知识一_第32张图片
    6.png
前端知识一_第33张图片
7.png

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

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

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

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

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

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


    前端知识一_第34张图片
    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事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。

前端知识一_第35张图片
1.png
  • addEventListener的第三个参数
element.addEventListener(event, function, useCapture);

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

  • 事件冒泡

  
父元素
子元素
前端知识一_第36张图片
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);
前端知识一_第37张图片
3.png

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

你可能感兴趣的:(前端知识一)