第十九节: JavsScript对象类型检测,克隆与JS异步加载问题

一、克隆对象 浅浅拷贝 浅拷贝 深拷贝

    //拷贝 => 赋值   浅浅拷贝    浅拷贝   深拷贝
    //对象之间的赋值叫浅浅拷贝,赋值的是内存地址,内存地址相同
    let obj = {
      name: '张三',
      age: 19,
      like: ['读书', '游泳']
    }
    let aa = obj;      //浅浅拷贝
    console.log('浅浅拷贝', aa == obj);


    let bb = {}
    for (let key in obj) {
      bb[key] = obj[key]     //浅拷贝
    }
    bb.like.push('乒乓球')
    console.log('浅拷贝', bb.like == obj.like); //内部的引用数据类型赋值的还是地址,所以内部的值还是有关系
    //深拷贝  长得一模一样,且各自不会受影响。赋值的是值,不是地址。采用递归,关注引用类型的值


    function clone(origin, target = {}) { //自己封装的深拷贝函数   造轮子
      for (let key in target) {
        if (target[key] !== null && typeof target[key] === 'object') {
          if (Object.prototype.toString.call(target[key]) === 'object Array') {//判断是不是等于数组,是数组就创建一个数组
            origin[key] = []
          } else {
            origin[key] = {} //不是数组就创建一个对象
          }
          clone(origin[key], target[key])  //递归
        } else {
          origin[key] = target[key]
        }

      } return origin
    }
    let origin = clone({}, obj)
    console.log('拷贝出来的值为', origin);
    console.log('深拷贝之后两个对象是否相等', origin == obj);

二、检测类型

    //封装的检测数据类型的函数
    function type(data) {
      if (data == null) { return null }
      let { toString } = Object.prototype; //从对象身上解构出来toString    
      switch (toString.call(data)) {    //解构前的写法(Object.prototype.toString.call(data))
        case "[object Array]": return 'array'
        case "{object Object}": return 'Object'
        case "[object RegExp]": return 'regexp'
      }
      return typeof data
    }
    console.log(type([]));
    console.log(type({}));
    console.log(type(/a/));
    console.log(type(function () { }));
    console.log(type(Symbol()));
    console.log(type(null));
    //封装检测数据类型的函数的简化写法 采用映射表
    function type(data) {
      if (data == null) { return null }
      let { toString } = Object.prototype;
      let typeMap = {
        "[object Array]": "Array",
        "[object Object]": "Object",
        "[object RegExp]": "RegExp",
        "[object Number]": "Number",
        "[object String]": "String",
        "[object Boolear]": "Boolear",
        "[object Undefined]": "Undefined",
        "[object Null]": "Null",
        "[object Symbol]": "Symbol",
        "[object Function]": "Function",
      }
      return typeMap[toString.call(data)]
    }
    console.log(type([]));
    console.log(type({}));
    console.log(type(/a/));
    console.log(type(function () { }));
    console.log(type(Symbol()));
    console.log(type(null));

三、 DOM树解析

什么是DOM树 ==>DOM节点按照树型结构排列

1. DOM树生成的原则

深度优先

2. 浏览器渲染页面前的步骤

  1. 解析DOM节点,生成DOM树(解构,构造)

  2. 继续解析css代码生成CSS树(样式,绘制)

  3. DOM树和CSS树合并在一块生成Render树(渲染树)

  4. 浏览器开始渲染绘制页面

3. script标签同步加载的弊端

js的加载是同步的;

3.1 js加载是同步的(一条路,代码前后执行)
  1. 同步加载是js为了防止js操作DOM元素的增、删、改、查,而造成问题

  2. js下载会阻止后面的标签执行,js还学会阻止网页中所有的下载行为

2.2 如果需要异步加载JS(两段代码并排执行)

比如要加载一些工具类的script文件,这些js文件并不会去操作DOM,也就不会对DOM结构和CSS产生影响.(明确知道js脚本不会操作节点的增删改查,引入外部的js脚本,这时就需要异步)

例如加载外部第三方的js。 https://www.bootcdn.cn/ 加速器 --> jquery -->https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js 复制链接

那么现在同步加载就会影响性能,因为如果没有加载完成,会阻塞后面的DOM结构的解析文件和css文件的加载,过多的工具类script文件,会让网页的加载时间过长。不利于用户体验

个别工具类script文件是按需加载,使用时才加载,不使用的时候就不加载

但有的时候我们是希望JS的加载是异步的

有些JS并不会操作页面,只是初始化一个数据,还有就是引入工具包吧,工具包就是一个function,你不调用压根不会执行,这样的js我们是不希望同步吧,因为如果在加载过程中有1k的内容没有加载过了,你整个程序就中断了,js会阻塞后面的内容

后来研究,就加载那些工具按需加载,用到再加载,不用不加载

2.3 JS操作DOM和CSS造成的重构和重绘

DOM树生成完成了在等着,等着CSS树生成

rander树一旦生成,页面就会绘制了吧

1.reflow 重构

如果在页面也就渲染完成了,你此时通过JS修改了DOM树,就会生成新的DOM树然后生成新的rander树,造成性能的浪费,所以即使你要修改DOM也请一次修改完,别修改一次 等一会 你在一次修改DOM树

重构效率是最低的,哪几种情况会触发重构呢

  • DOM节点的删除 添加,

  • DOM节点的宽高变换,位置变换 none - block

2.repaint 重绘,

改变背景原色,触发CSS树变换的叫重绘,这个重绘的只是一部分,并不会导致整个页面重构,

比如改个文字颜色啊,改个背景颜色啊,影响比较小,但是文字大小,改变 就会触发重构

重构可以理解为结构发生了变换,导致整体发生变化,重绘只是样式发生了变换,不会影响整体


  
2.3 异步加载js文件的三种方案

不给script加任何属性,js下载、执行都是同步的

  1. defer 异步加载属性 (异步下载js,最后DOM渲染完了,同步的执行)
    在script标签中加上defer属性。 (碰到带有defer 属性的js脚本仅仅下载,待DOM树渲染完后再执行js)

    在DOM文档解析完成之后才执行js文件,IE独有

  2. async (异步下载,下载完,DOM渲染停止,马上同步执行js)
    在加载完js文件之后就执行,只能加载外部js文件,不能把js代码写在js文件里
    js文件加载完成后理解执行.同时执行也是也不执行.不影响其他代码加载执行.
    这是w3c的方法,IE9以上都可以用,IE9及其以下不能用
    内嵌的JS不能使用
    非IE用的

  3. 创建script标签,插入在DOM中,在js文件加载完毕之后callBack
    能够实现按需加载
    封装兼容函数loadSript

var script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js';    // 这行代码只会下载js文件但不会执行,什么时候会执行呢,就是将标签加到body中

document.head.appendChild(script)

//因为加载js是异步的,所以这里有会有问题,js还没加载前,就讲script加到head中了
// 那有没有一个事件可以可以通知我们js加载完成了了呢
// load事件

    window.onload = function () {   //onload表示等待,等DOM渲染完了再下载js
      var script = document.createElement('script')
      script.type = 'text/javascript'
      script.src = 'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js';
      document.head.appendChild(script)
    }

但是IE 8及其以下身上没有onload事件,IE自己有一个特殊的状态码

script.readyState = 'lading';  // 
script.onreadystatechange = function(){
    if(script.readyState == 'loading' || script.readyState == 'complete'){
        text()
    }
}

总之一切的东西都是为了优化效率

封装loadScrip

function loadScript(url,callback){
    var script = document.createElement('script');
    script.type = 'text/javascript';
    // script.src = url;   

    if(script.readyState){   //如果有    if(script.readyState){   //如果有就表示是IE浏览器
表示是IE浏览器
        script.onreadystatechange = function(){
            if(script.readyState == 'loading' || script.readyState == 'complete'){
                callback()
            }
        }
    }else{
        script.onload = function (){
            callback()
        }
    }

    script.src = url;   
    document.head.appendChild(script);
}

loadScript('index2.js',function(){
    text();
})

封装的这个方法就可以按需加载按需执行.

四、 js的时间线

js出生时浏览器执行的事,记录浏览器执行的顺序

  1. 创建document对象。解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中。这个阶段document.readyState = 'loading'。
  2. 如果遇到link外部css文件,浏览器会创建新线程加载,同时继续解析文档。 异步下载
  3. 如果遇到外部js文件,并且没有设置async、defer,浏览器会加载js文件,阻塞主线程,等待js文件加载完成并执行该文件,然后继续解析文档。
  4. 如果遇到外部js文件,并且设置有async、defer,浏览器会创建新线程加载js文件,同时继续解析文档。对于async属性的js文件,会在js文件加载完成后立即同步执行。
  5. 如果遇到img,iframe等,浏览器在解析dom结构时,会异步加载src,同时继续解析文档。
  6. 当DOM文档解析完成,document.readyState = 'interactive’。
  7. 文档解析完成后,所有设置有defer的脚本会按照顺序执行。
  8. document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段。
  9. 当所有async的脚本加载完成并执行后、img等加载完成后,document.readyState = 'complete',window对象触发load事件
  10. 从此,以异步响应方式处理用户输入,网络事件等

  

    简化就三个步骤

    1.创建document对象

    2.文档解析完成

    3.文档加载完成

    console.log(document.readyState);   // 初始状态loading
    
    window.onload = function(){   //触发window.onload事件,表示DOM树渲染完毕,js执行完,img下载完 
        console.log(document.readyState)   // complete
    }
    document.onreadystatechange = function(){    //每次DOM改变是都会触发onreadystatechange事件
        console.log(document.readyState)    //interactive  complete
    }
    // 文档解析完成 onload太墨迹了 要全部加载完才执行
    document.addEventListener('DOMContentLoaded',function(){
        console.log(33)
    },false)
    

    你可能感兴趣的:(第十九节: JavsScript对象类型检测,克隆与JS异步加载问题)