每日思考(2019/12/29)

题目概览

  • js放在html的<body><head>有什么区别?
  • 说说浏览器解析CSS选择器的过程?
  • 对new操作符的理解

题目解答

js放在html的<body><head>有什么区别?

  • 在HTML body部分中的js会在页面加载的时候被执行。在HTML head部分中的js会在被调用的时候才执行,但是在主页和其余部分代码之前预先装载

    • js 放在<head> 中,如果不添加 async 或者 defer 时,当浏览器遇到 script 时,会阻塞 DOM 树的构建,进而影响页面的加载。当 js 文件较多时,页面白屏的时间也会变长。在这个过程中,如果解析器遇到了一个脚本(script),它就会停下来,并且执行这个脚本,然后才会继续解析 HTML。如果遇到了一个引用外部资源的脚本(script),它就必须停下来等待这个脚本资源的下载,而这个行为会导致一个或者多个的网络往返,并且会延迟页面的首次渲染时间。
    • 把 js 放到 <body> 里(一般在</body>的上面)时,由于 DOM 时顺序解析的,因此 js 不会阻塞 DOM 的解析。对于必须要在 DOM 解析前就要加载的 js,我们需要放在<head>
  • head 部分中的脚本: 需调用才执行的脚本或事件触发执行的脚本放在HTML的head部分中。当你把脚本放在head部分中时,可以保证脚本在任何调用之前被加载,从而可使代码的功能更强大; 比如对*.js文件的提前调用。 也就是说把代码放在<head>区在页面载入的时候,就同时载入了代码,你在<body>区调用时就不需要再载入代码了,速度就提高了

    <html>
    <head>
    <script type=”text/javascript”>
    ….
    </script>
    </head>
  • body 部分中的脚本: 当页面被加载时立即执行的脚本放在HTML的body部分。放在body部分的脚本通常被用来生成页面的内容

    <html>
    <head>
    </head>
    <body>
    <script type=”text/javascript”>
    ….
    </script>
    </body>
  • body 和 head 部分可同时有脚本:你可在文件中放无数的脚本,因此你的文件中可以在body和head部分同时存在脚本。

    <html>
    <head>
    <script type=”text/javascript”>
    ….
    </script>
    </head>
    <body>
    <script type=”text/javascript”>
    ….
    </script>
    </body>
  • 外部脚本的使用:有时候你可能想在几个页面中运行同样的脚本程序, 而不需在各个页面中重复的写这些代码。这时你就要用到外部脚本。你可以把脚本写在一个外部文件中,保存在扩展名为 .js的文件中。

    <html>
    <head>
    <script src=”xxx.js”></script>
    </head>
    <body>
    </body>
    </html>
  • 如果把javascript放在head里的话,则先被解析,但这时候body还没有解析。(常规html结构都是head在前,body在后)如果head的js代码是需要传入一个参数(在body中调用该方法时,才会传入参数),并需调用该参数进行一系列的操作,那么这时候肯定就会报错,因为函数该参数未定义(undefined)

  • 从JavaScript对页面下载性能方向考虑:由于脚本会阻塞其他资源的下载(如图片等)和页面渲染,直到脚本全部下载并执行完成后,页面的渲染才会继续,因此推荐将所有的<script>标签尽可能放到<body>标签的底部,以尽量减少对整个页面下载的影响

说说浏览器解析CSS选择器的过程?

  • 浏览器解析css选择器的规则是从右向左的,这样会提高查找选择器所对应的元素的效率

  • HTML 经过解析生成 DOM Tree;而在 CSS 解析完毕后,需要将解析的结果与 DOM Tree 的内容一起进行分析建立一棵 Render Tree,最终用来进行绘图。Render Tree 中的元素(WebKit 中称为「renderers」,Firefox 下为「frames」)与 DOM 元素相对应,但非一一对应:一个 DOM 元素可能会对应多个 renderer,如文本折行后,不同的「行」会成为 render tree 种不同的 renderer。也有的 DOM 元素被 Render Tree 完全无视,比如 display:none 的元素
  • 在建立 Render Tree 时(WebKit 中的「Attachment」过程),浏览器就要为每个 DOM Tree 中的元素根据 CSS 的解析结果(Style Rules)来确定生成怎样的 renderer。对于每个 DOM 元素,必须在所有 Style Rules 中找到符合的 selector 并将对应的规则进行合并。选择器的「解析」实际是在这里执行的,在遍历 DOM Tree 时,从 Style Rules 中去寻找对应的 selector
  • 因为所有样式规则可能数量很大,而且绝大多数不会匹配到当前的 DOM 元素(因为数量很大所以一般会建立规则索引树),所以有一个快速的方法来判断「这个 selector 不匹配当前元素」就是极其重要的。如果正向解析,例如「div div p em」,我们首先就要检查当前元素到 html 的整条路径,找到最上层的 div,再往下找,如果遇到不匹配就必须回到最上层那个 div,往下再去匹配选择器中的第一个 div,回溯若干次才能确定匹配与否,效率很低。逆向匹配则不同,如果当前的 DOM 元素是 div,而不是 selector 最后的 em,那只要一步就能排除。只有在匹配时,才会不断向上找父节点进行验证。但因为匹配的情况远远低于不匹配的情况,所以逆向匹配带来的优势是巨大的。同时我们也能够看出,在选择器结尾加上「*」就大大降低了这种优势,这也就是很多优化原则提到的尽量避免在选择器末尾添加通配符的原因
  • CSS优化:
    • 不要在id选择器前使用标签名,如div#box,因为id选择器是惟一的,加上div反而增加不必要的css匹配,多此一举
    • 使用 class 代替层级关系,比如#box ul li a{}写成.box-a{}。在CSS渲染效率中,id和class的效率是基本相当的,class会在第一次载入中被缓存,在层叠中会有更好的效果,在根部元素采用id会有微妙的速度优势

对new操作符的理解

  • 含义:new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

  • 步骤:模拟new操作前,要先知道new操作是发生了什么,就拿new Object()举例

    • 创建一个新对象
    • 把新对象的原型指向构造函数的prototype
    • 把构造函数里的this指向新对象
    • 返回这个新对象
  • 构造函数与原生new

    function constructorFunction(name, age){
      this.name = name;
      this.age = age;
    }
    constructorFunction.prototype.say = function(){
      return 'Hello '+ this.name
    }
    var obj = new constructorFunction('eric', 18)
    console.log(obj.name, obj.age);//'eric', 18
    console.log(obj.say())//Hello eric
  • 模拟new

    function newNew(){
     var newObj = {}
     // 1. 创建一个新对象
     var Con = [].shift.call(arguments)
     // 得到构造函数
     newObj.__proto__ = Con.prototype;
     // 2. 把新对象的原型指向构造函数的prototype
     var res = Con.apply(newObj, arguments)
     // 3. 把构造函数里的this指向新对象
     return typeof res === 'object' ? res : newObj;
     // 4. 返回新对象
    }
    var obj = newNew(constructorFunction, 'eric', 18)
    console.log(obj.name, obj.age);//'eric', 18
    console.log(obj.say())//Hello eric

相关推荐