JavaScript单线程、加载与模块化

JavaScript单线程与浏览器多线程

  1. Javascript是单线程的:因为JS运行在浏览器中,是单线程的,每个window一个JS线程。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。若以多线程的方式操作这些DOM,则可能出现操作的冲突。假设有两个线程同时操作一个DOM元素,线程1要求浏览器删除DOM,而线程2却要求修改DOM样式,这时浏览器就无法决定采用哪个线程的操作。
  2. 浏览器不是单线程的,引擎可能存在如下线程:

    • javascript引擎线程
    • 界面渲染线程
    • 浏览器事件触发线程
    • Http请求线程等

Event Loop

事件循环机制:
主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在”任务队列”中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取”任务队列”,依次执行那些事件所对应的回调函数。

  • 执行栈:所有同步任务都在主线程上执行,形成一个执行栈;
  • 任务队列:主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件,当栈为空时,就会从任务队列中取出一个任务并执行。

定时器:
定时器包括setTimeout与setInterval两个方法。它们的第二个参数是指定其回调函数推迟每隔多少毫秒数后执行。
零延迟 setTimeout(func, 0):零延迟并不是意味着回调函数立刻执行。它取决于主线程当前是否空闲与“任务队列”里其前面正在等待的任务。当计时器时间规定的时候,回调函数会被添加到“任务队列”中,等到执行栈的任务执行完毕,就会来执行这里的任务。所以,有的时候即使计时器已经到0了,也会不立即执行计时器中的回调任务。

Ajax异步请求:
在发起ajax请求的时候,浏览器会开一个线程去执行这一步骤,发请求这一步是属于执行栈中的同步任务,在请求发完之后就会执行栈中的下一个任务,而等到请求有了结果,就会把结果放入“任务队列”中,等待执行;

JavaScript异步编程

JavaScript是单线程的,所谓“单线程”,就是同一时间只能执行一个任务。如果有多个任务,就必须排队,直到前一个任务完成。这种模式的好处就是实现比较简单,尤其在浏览器环境中,可以避免很多不必要的麻烦。坏处就是如果一个任务耗时很长,那么该任务后面的任务就必须一直等待,导致整个页面都卡住无法继续往下执行。

为了解决这个问题,JavaScript将任务的执行模式分为两类,一个是同步模式,另一个是异步模式。

“同步模式”就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;”异步模式”则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

JS异步编程的4中方法包括:

  • 回调函数:

    f1();
    f2();
    
    function f1(callback){
        setTimeout(function(){
            callback();
        })
    }
    
    f1(f2);
  • 事件监听:采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

    //jQuery
    f1.on('done', f2);
    
    function f1(){
       setTimeout(function () {
           //do something
         f1.trigger('done');//执行完成后,立即触发done事件,从而开始执行f2。
       }, 1000);
    }
  • 发布/订阅:又称观察者模式

    jQuery.subscribe("done", f2);//订阅'done'这个信号
    
    function f1(){
       setTimeout(function () {
         //do something
         jQuery.publish("done");//f1()执行完之后,发出'done'这个信号,f2开始执行
       }, 1000);
    }
    
    jQuery.unsubscribe("done", f2);//取消订阅
  • Promises:ES6原生提供了Promise对象。所谓Promise对象,就是代表了未来某个将要发生的事件(通常是一个异步操作)。它的好处在于,有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象还提供了一整套完整的接口,使得可以更加容易地控制异步操作。

    var promise = new Promise(function(resolve, reject) {
      if (/* 异步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    
    promise.then(function(value) {
      // success
    }, function(value) {
      // failure
    });

ES6模块管理

ECMAScript 6基于export和import,定义了模块的导出和导入规范,在语言标准层面实现了模块机制。该标准的目标是创建一种能够兼容CommoneJS和AMD两标准的规范,即可以像CommoneJS一样语法简洁、使用单一的接口且支持循环依赖,又可以像AMD支持异步加载和可配置的模块加载。

大致来说,当 JS 引擎运行一个模块的时候,它的行为大致可归纳为以下四步:

  1. 解析:引擎实现会阅读模块的源码,并且检查是否有语法错误。
  2. 加载:引擎实现会(递归地)加载所有被引入的模块。
  3. 链接:引擎实现会为每个新加载的模块创建一个作用域,并且将模块中的声明绑定填入其中,包括从其他模块中引入的。

JS加载

动态加载常用的4种方法:

  1. document.write:document.write("<script src='package.js'><\/script>");
  2. 动态改变已有script的src属性:js.src = "package.js";
  3. 动态创建script元素:

    var script = document.createElement("script");
    script.src = "XXX";
    script.type = "XXX";
    document.head.appendChild(script);
  4. 用XMLHTTP取得要脚本的内容,再创建 Script 对象。

项目中加载JS的方法:
在页面底部写一个自调用函数,加载入口JS,然后入口中的方法执行,加载需要的其他JS。

<script>
    (function(G,D){var el=D.createElement('script'),d="XXX';
        el.src=((G._moduleConfig&&G._moduleConfig.domain)?G._moduleConfig.domain:d)+"XXX?t=XXX";
        D.getElementsByTagName('head')[0].appendChild(el);
    })(this,document);
</script>

在VM平台上,可以选择将几个模块分组打包到一个入口文件中,然后在这个入口文件中,将每个组动态生成一个script标签,插入head中。

异步加载:
浏览器在渲染一个页面的时候,遇到一个<script>标签就会去下载对应的js文件,如果采用同步加载JS的方式,会造成页面阻塞,直到JS加载完成再继续渲染,所以有了异步加载JS的形式。

常见的异步加载JS方式,将js代码包裹在匿名函数中并立即执行,动态生成<script>标签去拉取js,可以实现多个js文件并行下载,只有在解析执行的时候会阻塞渲染。另外,可以使用script标签的asyncdefer属性。

延迟加载:
延迟加载就是一开始并不加载这些暂时不用的js,而是在需要的时候或稍后再通过js 的控制来异步加载。
也就是将 js 切分成许多模块,页面初始化时只加载需要立即执行的 js ,然后其它 js 的加载延迟到第一次需要用到的时候再加载。
特别是页面有大量不同的模块组成,很多可能暂时不用或根本就没用到。

script的两阶段加载与延迟执行
JS的加载其实是由两阶段组成:下载内容(download bytes)和执行(parse and execute)。
浏览器在下载完 js 的内容后就会立即对其解析和执行,不管是同步加载还是异步加载。
前面说的异步加载,解决的只是下载阶段的问题,但代码在下载后会立即执行。
而浏览器在解析执行 JS 阶段是阻塞任何操作的,这时的浏览器处于无响应状态。
我们都知道通过网络下载 script 需要明显的时间,但容易忽略了第二阶段,解析和执行也是需要时间的。script的解析和执行所花的时间比我们想象的要多,尤其是script 很多很大的时候。有些是需要立刻执行,而有些则不需要(比如只是在展示某个界面或执行某个操作时才需要)。
这些script 可以延迟执行,先异步下载缓存起来,但不立即执行,而是在第一次需要的时候执行一次。
利用特殊的技巧可以做到下载与执行的分离。比如将 JS 的内容作为object对象加载缓存起来,所以就不会立即执行了,然后在第一次需要的时候再执行。

SQL相关

  1. 多表查询:

    • 定义:根据特定的连接条件从不同的表中获取所需的数据;
    • 语法:SELECT table1.column, table2.column FROM table1, table2 WHERE table1.column1 = table2.column2;
    • 注意:where不要省了,省略where即为笛卡尔集,而且where条件要有效,两张表间有一个相同的字段,才好进行有效的多表查询;
    • 简化:为了简化SQL书写,可为表名定义别名,格式:from 表名别名,如:from emp e,dept d;
    • 优化:建议使用表的别名及表前缀,使用表别名可以简化查询,而使用表前缀则可以提高查询性能;
  2. 连接类型:从数据显示方式来讲有:内连接和外连接。

    • 内连接:只返回满足连接条件的数据。

      • 例:select empno,ename,sal,dname,loc from emp,dept where emp.deptno=dept.deptno;
      • 等值连接:在连接条件中使用等于号(=)运算符比较被连接列的列值,其查询结果中列出被连接表中的所有列,包括其中的重复列。
      • 不等值连接:在连接条件使用除等于运算符以外的其它比较运算符比较被连接的列的列值。这些运算符包括>、>=、<=、<、!>、!<和<>。
      • 自然连接:在连接条件中使用等于(=)运算符比较被连接列的列值,但它使用选择列表指出查询结果集合中所包括的列,并删除连接表中的重复列。
    • 外连接:除了返回满足连接条的行以外,还返回左(右)表中,不满足条件的行,称为左(右)连接;

      • 左外连接:是以左表为基准,将左表没有的对应项显示,右表的列为NULL

        • select * from book as a left join stu as b on a.sutid = b.stuid
      • 右外连接:是以右表为基准,将a.stuid = b.stuid的数据进行连接,然以将右表没有的对应项显示,左表的列为NULL

        • select * from book as a right join stu as b on a.sutid = b.stuid
      • 全连接:完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。

        • select * from book as a full outer join stu as b on a.sutid = b.stuid
  3. CI框架常用的数据库操作:

    • 常规查询:$this->db->query('YOUR QUERY HERE');
    • 查询绑定:查询绑定可以简化你的查询语法,它通过系统自动的为你将各个查询组装在一起。 参考下面的例子:
    $sql = "SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?";
    $this->db->query($sql, array(3, 'live', 'Rick'));
    • $this->db->count_all():该方法用于获取数据表的总行数,第一个参数为表名;
    • 查询构造器类:odeIgniter 提供了查询构造器类,查询构造器允许你使用较少的代码来在数据库中 获取、新增或更新数据。有时只需要一两行代码就能完成数据库操作。CodeIgniter 并不需要为每个数据表提供一个类,而是使用了一种更简单的接口。

      • 查询:
      $query = $this->db->get('mytable');  // Produces: SELECT * FROM mytable
      
      $query = $this->db->get('mytable', 10, 20);
      
      // Executes: SELECT * FROM mytable LIMIT 20, 10
      // (in MySQL. Other databases have slightly different syntax)
      • $this->db->select():该方法用于编写查询语句中的 SELECT 子句:
      $this->db->select('title, content, date');
      $query = $this->db->get('mytable');
      
      // Executes: SELECT title, content, date FROM mytable
      • $this->db->from():该方法用于编写查询语句中的 FROM 子句:
      $this->db->select('title, content, date');
      $this->db->from('mytable');
      $query = $this->db->get();  // Produces: SELECT title, content, date FROM mytable
      • $this->db->join():该方法用于编写查询语句中的 JOIN 子句:
      $this->db->select('*');
      $this->db->from('blogs');
      $this->db->join('comments', 'comments.id = blogs.id');
      $query = $this->db->get();
      
      // Produces:
      // SELECT * FROM blogs JOIN comments ON comments.id = blogs.id
      
      //你可以传入第三个参数指定连接的类型,有这样几种选择:left,right,outer,inner,left outer 和 right outer 。
      
      $this->db->join('comments', 'comments.id = blogs.id', 'left');
      // Produces: LEFT JOIN comments ON comments.id = blogs.id
      • $this->db->where():该方法提供了4中方式让你编写查询语句中的 WHERE 子句(所有的数据将会自动转义,生成安全的查询语句。):
      1.简单的 key/value 方式:
      
      $this->db->where('name', $name); // Produces: WHERE name = 'Joe'
      注意自动为你加上了等号。
      
      如果你多次调用该方法,那么多个 WHERE 条件将会使用 AND 连接起来:
      
      $this->db->where('name', $name);
      $this->db->where('title', $title);
      $this->db->where('status', $status);
      // WHERE name = 'Joe' AND title = 'boss' AND status = 'active'
      
      2.自定义 key/value 方式:
      
      为了控制比较,你可以在第一个参数中包含一个比较运算符:
      
      $this->db->where('name !=', $name);
      $this->db->where('id <', $id); // Produces: WHERE name != 'Joe' AND id < 45
      
      3.关联数组方式:
      
      $array = array('name' => $name, 'title' => $title, 'status' => $status);
      $this->db->where($array);
      // Produces: WHERE name = 'Joe' AND title = 'boss' AND status = 'active'
      你也可以在这个方法里包含你自己的比较运算符:
      
      $array = array('name !=' => $name, 'id <' => $id, 'date >' => $date);
      $this->db->where($array);
      
      4.自定义字符串:
      
      你可以完全手工编写 WHERE 子句:
      
      $where = "name='Joe' AND status='boss' OR status='active'";
      $this->db->where($where);
      $this->db->where() 方法有一个可选的第三个参数,如果设置为 FALSE,CodeIgniter 将不保护你的表名和字段名。
      
      $this->db->where('MATCH (field) AGAINST ("value")', NULL, FALSE);
      • $this->db->like():该方法用于生成 LIKE 子句,在进行搜索时非常有用。
      简单 key/value 方式:
      
      $this->db->like('title', 'match');
      // Produces: WHERE `title` LIKE '%match%' ESCAPE '!'
      如果你多次调用该方法,那么多个 WHERE 条件将会使用 AND 连接起来:
      
      $this->db->like('title', 'match');
      $this->db->like('body', 'match');
      // WHERE `title` LIKE '%match%' ESCAPE '!' AND  `body` LIKE '%match% ESCAPE '!'
      可以传入第三个可选的参数来控制 LIKE 通配符(%)的位置,可用选项有:'before','after' 和 'both' (默认为 'both')。
      
      $this->db->like('title', 'match', 'before');    // Produces: WHERE `title` LIKE '%match' ESCAPE '!'
      $this->db->like('title', 'match', 'after'); // Produces: WHERE `title` LIKE 'match%' ESCAPE '!'
      $this->db->like('title', 'match', 'both');  // Produces: WHERE `title` LIKE '%match%' ESCAPE '!'
      关联数组方式:
      
      $array = array('title' => $match, 'page1' => $match, 'page2' => $match);
      $this->db->like($array);
      // WHERE `title` LIKE '%match%' ESCAPE '!' AND  `page1` LIKE '%match%' ESCAPE '!' AND  `page2` LIKE '%match%' ESCAPE '!'
      • $this->db->group_by():$this->db->group_by("title"); // Produces: GROUP BY title;
      • $this->db->order_by():$this->db->order_by('title', 'DESC');
      • $this->db->limit():$this->db->limit(10); // Produces: LIMIT 10
      • $this->db->count_all_results():该方法用于获取特定查询返回结果的数量,也可以使用查询构造器的这些方法: where(),or_where(),like(),or_like() 等等。
      • $this->db->delete():$this->db->delete('mytable', array('id' => $id)); // Produces: // DELETE FROM mytable // WHERE id = $id

为什么chrome浏览器的速度很快

  1. Chrome 把浏览器上做的每件事都拆成独立的进程,每个tab都是一个独立的进程,并利用进程间通讯来完成它们之间的同步,windows系统下可以通过任务管理器看到许多chrome 标签页的进程。
  2. 在还没点击 URL 之前,Chrome 已经在帮用户加载了

参考文章:
JavaScript的加载和执行性能优化
Javascript 异步加载详解
ES6 的模块系统
seaJs学习笔记之seaJs的异步加载和加载多个js文件
Promise对象
Javascript异步编程的4种方法
JavaScript 运行机制详解:再谈Event Loop
JavaScript:彻底理解同步、异步和事件循环(Event Loop)
JavaScript单线程和浏览器事件循环简述
Javascript是单线程的深入分析
查询辅助函数
SQL的几种连接:内连接、左联接、右连接、全连接、交叉连接
SQL基础–>多表查询
CHROME进程间通信
浏览器是如何工作的
浏览器是怎样工作的:渲染引擎,HTML解析
等……

    原文作者:SQL
    原文地址: https://segmentfault.com/a/1190000005863602
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞