js-Promise

Promise

ES6新增的构造函数Promise,用来处理异步的程序(为了解决回调地狱)

介绍

  1. 开启承诺:三天之后放假,然后呢:成功了,去哪玩,失败了,扫码

     如同:开启ajax请求,请求资源,成功了,渲染页面,失败了,报错
    
  2. 承诺正在进行时

  3. 承诺失败OR承诺成功

语法

new Promise( function(resolve, reject) {...} /* executor */  );
var p = new Promise(function(a,b){
    // 正在进行时....
    setTimeout(() => {
        a();
    }, Math.random()*1000);
    setTimeout(() => {
        b();
    }, Math.random()*1000);
});
p.then(function(){
    // 成功时,要做的事情
    // .....
    console.log(1)
},function(){
    // 失败时,要做的事情
    // .....
    console.log(2)
})

参数

  • executor

    executor是带有 resolvereject 两个参数的函数 。Promise构造函数执行时立即调用executor 函数, resolvereject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回所建promise实例对象前被调用)。resolvereject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject 函数将promise的状态改为rejected。如果在executor函数中抛出一个错误,那么该promise 状态为rejected。executor函数的返回值被忽略。

描述

**Promise** 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象

一个 Promise有以下几种状态:

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。

因为 Promise.prototype.thenPromise.prototype.catch 方法返回promise 对象, 所以它们可以被链式调用。

img

不要和惰性求值混淆: 有一些语言中有惰性求值和延时计算的特性,它们也被称为“promises”,例如Scheme. Javascript中的promise代表一种已经发生的状态, 而且可以通过回调方法链在一起。 如果你想要的是表达式的延时计算,考虑无参数的”箭头方法“: f = () =>表达式 创建惰性求值的表达式使用 f() 求值。

注意: 如果一个promise对象处在fulfilled或rejected状态而不是pending状态,那么它也可以被称为settled状态。你可能也会听到一个术语resolved ,它表示promise对象处于settled状态。关于promise的术语, Domenic Denicola 的 States and fates 有更多详情可供参考。

方法

Promise.all(iterable)

​ 这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。(可以参考jQuery.when方法—译者注)

Promise.race(iterable)

​ 当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

Promise.reject(reason)

​ 返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法

Promise.resolve(value)

​ 返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

Promise 原型

属性

  • Promise.prototype.constructor

    返回被创建的实例函数. 默认为 Promise 函数.

方法

  • Promise.prototype.catch(onRejected)

    添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的promise。当这个回调函数被调用,新 promise 将以它的返回值来resolve,否则如果当前promise 进入fulfilled状态,则以当前promise的完成结果作为新promise的完成结果.

  • Promise.prototype.then(onFulfilled, onRejected)

    添加解决(fulfillment)和拒绝(rejection)回调到当前 promise, 返回一个新的 promise, 将以回调的返回值来resolve.

  • Promise.prototype.finally(onFinally)

    添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)

promise改造ajax&jsonp

ajaxget封装

url1 = "http://127.0.0.1/myown/promise/php/data1.php"
url2 = "http://127.0.0.1/myown/promise/php/data2.php"
url3 = "http://127.0.0.1/myown/promise/php/data3.php"

function getAjax(url, data) {
    var p = new Promise(function (success, error) {
        var str = "";
        data = data || {};
        var xhr = new XMLHttpRequest();
        for (var i in data) {
            str += `${i}=${data[i]}&`;
        }
        str = str.slice(0, str.length - 1);
        url = url + "?" + str + "__retr0__=" + new Date().getDate();
        xhr.open("get", url, true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                success(xhr.responseText);
            } else if (xhr.readyState === 4 && xhr.status != 200) {
                error(xhr.status);
            }
        }
        xhr.send();
    })
    return p;
}
var p1 = getAjax(url1)
var p2 = getAjax(url2)
var p3 = getAjax(url3)
Promise.all([p1, p2, p3]).then(function (res) {
    console.log(res);
}, function (res) {
    console.log(res)
})

ajaxpost封装

url1 = "http://127.0.0.1/myown/promise/php/data1.php";
url2 = "http://127.0.0.1/myown/promise/php/data2.php";
url3 = "http://127.0.0.1/myown/promise/php/data3.php";

function postAjax(url, data) {
    var p = new Promise(function (success, error) {
        data = data || {};
        var str = "";
        var xhr = new XMLHttpRequest();
        for (var i in data) {
            str += `${i}=${data[i]}&`;
        }
        xhr.open("post", url, true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {

                success(xhr.responseText);
            } else if (xhr.readyState === 4 && xhr.status != 200) {
                error(xhr.status);
            }
        }
        console.log(1)
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        xhr.send(str);
    })
    return p;
}
var p1 = postAjax(url1);
var p2 = postAjax(url2);
var p3 = postAjax(url3);
Promise.all([p1, p2, p3]).then(function (res) {
    console.log(res);
}, function (res) {
    console.log(res);
})

jsonp封装

JS

url1 = "http://127.0.0.1/myown/promise/php/jsonp1.php";
url2 = "http://127.0.0.1/myown/promise/php/jsonp2.php";
url3 = "http://127.0.0.1/myown/promise/php/jsonp3.php";

function jsonp(url, data) {
    var p = new Promise(function (success) {
        var str = "";
        var script = document.createElement("script");
        for (var i in data) {
            str += `${i}=${data[i]}&`;
        }
        url = url + "?" + str + "__retr0__=" + new Date().getTime();
        script.src = url;
        window[data[data.cbName]] = function (res) {
            success(res);
        }
        document.body.appendChild(script);
    })
    return p;
}
var p1 = jsonp(url1, {
    user: "admin",
    pass: "admi",
    cbName: "callback",
    callback: "qqq"
});
var p2 = jsonp(url2, {
    user: "root",
    pass: "root",
    cbName: "callback",
    callback: "bbb"
});
var p3 = jsonp(url3, {
    user: "retr0",
    pass: "retr",
    cbName: "callback",
    callback: "ccc"
});
Promise.all([p1, p2, p3]).then(function (res) {
    console.log(res)
})

PHP

<?php
$u = $_GET["user"];
$p = $_GET["pass"];
$cb = $_GET["callback"];
$data = "这是JSONP接受到的数据" . $u . "------" . $p;
echo "$cb('" . $data . "')";
?>

js-storage

storage

localStorage

只读的localStorage 属性允许你访问一个Document 源(origin)的对象 Storage;存储的数据将保存在浏览器会话中。localStorage 类似 sessionStorage,但其区别在于:存储在 localStorage 的数据可以长期保留;而当页面会话结束——也就是说,当页面被关闭时,存储在 sessionStorage 的数据会被清除 。

应注意,无论数据存储在 localStorage 还是 sessionStorage它们都特定于页面的协议。

另外,localStorage 中的键值对总是以字符串的形式存储。 (需要注意, 和js对象相比, 键值对总是以字符串的形式存储意味着数值类型会自动转化为字符串类型).

语法

myStorage = localStorage;

一个可被用于访问当前源( origin )的本地存储空间的 Storage 对象。

异常

  • SecurityError

    请求违反了一个策略声明,或者源( origin )不是 一个有效的 scheme/host/port tuple (例如如果origin使用 file: 或者 data: 形式将可能发生)。比如,用户可以有禁用允许对指定的origin存留数据的浏览器配置。

下面的代码片段访问了当前域名下的本地 Storage 对象,并通过 Storage.setItem() 增加了一个数据项目。

.setItem()

localStorage.setItem('myCat', 'Tom');

.getItem()

该语法用于读取 localStorage 项,如下:

let cat = localStorage.getItem('myCat');

.removeItem()

该语法用于移除 localStorage 项,如下:

localStorage.removeItem('myCat');

.clear()

该语法用于移除所有的 localStorage 项,如下:

// 移除所有
localStorage.clear();

localStorage是对象,有自己的属性或值

localStorage.user = “admin”;

localStorage.user = “root”;

console.log(localStorage.user)

delete localStorage.user

sessionStorage

sessionStorage 属性允许你访问一个 session Storage 对象。它与 localStorage 相似,不同之处在于 localStorage 里面存储的数据没有过期时间设置,而存储在 sessionStorage 里面的数据在页面会话结束时会被清除。页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文,这点和 session cookies 的运行方式不同。

应该注意,存储在sessionStorage或localStorage中的数据特定于该页面的协议

语法

// 保存数据到 sessionStorage
sessionStorage.setItem('key', 'value');

// 从 sessionStorage 获取数据
let data = sessionStorage.getItem('key');

// 从 sessionStorage 删除保存的数据
sessionStorage.removeItem('key');

// 从 sessionStorage 删除所有保存的数据
sessionStorage.clear();

返回值

一个 Storage 对象。

window.onStorage事件

WindowEventHandlers.onstorage 属性包含一个在storage事件触发时的事件句柄。 当存储域发生改变时会触发事件。(例如: 有新的项被存储)

专门用来检测storage的变化

不能直接使用,只能检测非当前页面的storage的变化,而且只能检测同一个服务器环境下的页面

event.key

值发生变化的key

event.oldValue

改变前的值

event.newValue

改变后的值

实例

window.onstorage = function(e) {
  console.log( e.key + ' 键已经从 ' + e.oldValue + ' 改变为 ' + e.newValue + '.');
};

js-cookie

计算机通信协议

IP

ip地址,标记,接入互联网的每台设备的唯一身份

TCP

数据:面向连接的协议,可靠,三次握手协议

UDP

数据:面向数据的协议,不可靠,容易造成数据丢失

HTTP

网页,超文本传输协议,html,无状态协议,为了安全,节约,http每次建立连接,传输数据之后,会自动断开

会话跟踪技术,帮助http记住状态

cookie的介绍

用来记录客户端到服务器的一次连接过程中产生的各种状态

前后端数据交互的格式:字符

cookie记录状态

  • 怎么给服务器?跟随http协议,发往服务器
  • 记录在哪?本地浏览器的缓存中,在硬盘上,不是内存
  • 以什么形式记录?字符
  • 大小限制:4K左右,1K=1024字节,1个中文,找2个字节
  • 条数限制:50条左右
  • 时间限制:默认是会话级,关闭浏览器自动删除;随意设置,没有永久,必须指定时间
  • 使用限制:不允许跨域,不允许跨浏览器,不允许跨路径(父文件夹不能拿子文件夹中的cookie)

cookie的属性

path

​ path表示cookie所在的目录,asp.net默认为/,就是根目录。

​ 在同一个服务器上有目录如下:/test/,/test/cd/,/test/dd/,现设一个cookie1的path为/test/,cookie2的path为/test/cd/,那么test下的所有页面都可以访问到cookie1,而/test/和/test/dd/的子页面不能访问cookie2。这是因为cookie能让其path路径下的页面访问。

expires

​ 指定了cookie的生存期,默认情况下cookie是暂时存在的,他们存储的值只在浏览器会话期间存在,当用户退出浏览器后这些值也会丢失,如果想让cookie存在一段时间,就要为expires属性设置为未来的一个用毫秒数表示的过期日期或时间点,expires默认为设置的expires的当前时间。现在已经被max-age属性所取代,max-age用秒来设置cookie的生存期。

​ 如果max-age属性为正数,则表示该cookie会在max-age秒之后自动失效。浏览器会将max-age为正数的cookie持久化,即写到对应的cookie文件中。无论客户关闭了浏览器还是电脑,只要还在max-age秒之前,登录网站时该cookie仍然有效。

​ 如果max-age为负数,则表示该cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该cookie即失效。max-age为负数的Cookie,为临时性cookie,不会被持久化,不会被写到cookie文件中。cookie信息保存在浏览器内存中,因此关闭浏览器该cookie就消失了。cookie默认的max-age值为-1。

​ 如果max-age为0,则表示删除该cookie。cookie机制没有提供删除cookie的方法,因此通过设置该cookie即时失效实现删除cookie的效果。失效的Cookie会被浏览器从cookie文件或者内存中删除。

如果不设置expires或者max-age这个cookie默认是Session的,也就是关闭浏览器该cookie就消失了。

​ 这里要说明一下:Session的cookie在ie6下,如果用户实在网页上跳转打开页面或新开窗口(包括target=”_blank”,鼠标右键新开窗口),都是在同一个Session内。如果用户新开浏览器程序或者说是进程再打开当前的页面就不是同一个Session。其他浏览器只要你Session存在,还是同一个Session,cookie还能共享。在前段时间的项目中ie6下吃了很大一个亏。

封装cookie

function setCookie(key, val, options) {
    options = options || {};
    var time = "";
    if (options.Expires) {
        var d = new Date();
        d.setDate(d.getDate() + options.Expires);
        time = ";expires=" + d;
    }
    var path = "";
    path = options.path ? ";path=" + options.path : "";
    document.cookie = key + "=" + val + time + path;
}
setCookie("user","admin",{
    Expires:7,
    path:"/cookie"
})

function setCookie(key, val, options) {
    options = options || {};
    var time = "";
    if (options.Expires) {
        var d = new Date();
        d.setDate(d.getDate() + options.Expires);
        time = ";expires=" + d;
    }
    var path = "";
    path = options.path ? ";path=" + options.path : "";
    document.cookie = key + "=" + val + time + path;
}
setCookie("user","admin",{
    Expires:7
})
setCookie("user","admin",{
    Expires:-1
})//利用cookie有效期,将有效期调整为当前日期的前一天即可销毁

function setCookie(key, val, options) {
    options = options || {};
    var time = "";
    if (options.Expires) {
        var d = new Date();
        d.setDate(d.getDate() + options.Expires);
        time = ";expires=" + d;
    }
    var path = "";
    path = options.path ? ";path=" + options.path : "";
    document.cookie = key + "=" + val + time + path;
}
setCookie("user","admin",{
    Expires:7
})
setCookie("user","123456",{
    Expires:6
})

function getCookie(key) {
    var arr = document.cookie.split("; ");
    var v = "";
    arr.forEach((val) => {
        if (val.split("=")[0] === key) {
            v = val.split("=")[1];
        }
    })
    return v;
}

js-JSONP

JSONP

利用 script 标签可以跨域,让服务器端返回可执行的Javascript函数,参数为要回发的数据。

同源策略

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

定义

如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的。我们也可以把它称为“协议/主机/端口 tuple”,或简单地叫做“tuple”. (“tuple” ,“元”,是指一些事物组合在一起形成一个整体,比如(1,2)叫二元,(1,2,3)叫三元)

下表给出了相对http://store.company.com/dir/page.html同源检测的示例:

URL 结果 原因
http://store.company.com/dir2/other.html 成功 只有路径不同
http://store.company.com/dir/inner/another.html 成功 只有路径不同
https://store.company.com/secure.html 失败 不同协议 ( https和http )
http://store.company.com:81/dir/etc.html 失败 不同端口 ( http:// 80是默认的)
http://news.company.com/dir/other.html 失败 不同域名 ( news和store )

JSON和JSONP

JSONP和JSON好像啊,他们之间有什么联系吗?

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。对于JSON大家应该是很了解了吧,不是很清楚的朋友可以去json.org上了解下,简单易懂。

JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。–来源百度

  JSONP就像是JSON+Padding一样(Padding这里我们理解为填充), 我们先看下面的小例子然后再详细介绍。

实例

JS代码

var url = "http://localhost/myown/jsonp/jsonp2.php";
document.onclick = function () {
    jsonp(url, (res) => {
        console.log(res);
    }, {
        cb: "qwe",
        cbname: "cb",
        user: "root",
        pass: "root"
    })
}

function jsonp(url, callback, obj) {
    var str = "";
    var script = document.createElement("script");
    for (var i in obj) {
        str += `${i}=${obj[i]}&`;
    }
    url = url + "?" + str + "__retr0__=" + new Date().getTime();
    script.src = url;
    document.body.appendChild(script);
    window[obj[obj.cbname]] = function (res) {
            callback(res);
    }
    script.remove();
}

php代码

<?php
$u = $_GET["user"];
$p = $_GET["pass"];
$cb = $_GET["cb"];
$data = "这是JSONP接受到的数据" . $u . "------" . $p;
echo "$cb('" . $data . "')";
?>

js-Event-Loop

Event-Loop

JavaScript的运行机制

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。

(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步

概括即是: 调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作

一个事件循环中有一个或者是多个任务队列

异步任务分类

  1. 宏任务: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
  2. 微任务: process.nextTick(Nodejs), Promises, Object.observe, MutationObserver

Event-Loop定义

主线程从”任务队列”中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:主线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查microtask队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去任务队列中取下一个任务执行。

详细说明:

  1. 选择当前要执行的宏任务队列,选择一个最先进入任务队列的宏任务,如果没有宏任务可以选择,则会跳转至microtask的执行步骤。
  2. 将事件循环的当前运行宏任务设置为已选择的宏任务。
  3. 运行宏任务。
  4. 将事件循环的当前运行任务设置为null。
  5. 将运行完的宏任务从宏任务队列中移除。
  6. microtasks步骤:进入microtask检查点。
  7. 更新界面渲染。
  8. 返回第一步。

执行进入microtask检查的的具体步骤如下:

  1. 设置进入microtask检查点的标志为true。
  2. 当事件循环的微任务队列不为空时:选择一个最先进入microtask队列的microtask;设置事件循环的当前运行任务为已选择的microtask;运行microtask;设置事件循环的当前运行任务为null;将运行结束的microtask从microtask队列中移除。
  3. 对于相应事件循环的每个环境设置对象(environment settings object),通知它们哪些promise为rejected。
  4. 清理indexedDB的事务。
  5. 设置进入microtask检查点的标志为false。

需要注意的是:当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件, 然后再去宏任务队列中取出一个事件。同一次事件循环中, 微任务永远在宏任务之前执行。

图示:

event-loop2

先看一个简单的示例:

setTimeout(()=>{
    console.log("setTimeout1");
    Promise.resolve().then(data => {
        console.log(222);
    });
});
setTimeout(()=>{
    console.log("setTimeout2");
});
Promise.resolve().then(data=>{
    console.log(111);
});

思考一下, 运行结果是什么?

运行结果为:

111
setTimeout1
222
setTimeout2

我们来看一下为什么?

我们来详细说明一下, JS引擎是如何执行这段代码的:

  1. 主线程上没有需要执行的代码
  2. 接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中(这个任务在下一次的事件循环中执行)。
  3. 接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中(这个任务在再下一次的事件循环中执行)。
  4. 首先检查微任务队列, 即 microtask队列,发现此队列不为空,执行第一个promise的then回调,输出 ‘111’。
  5. 此时microtask队列为空,进入下一个事件循环, 检查宏任务队列,发现有 setTimeout的回调函数,立即执行回调函数输出 ‘setTimeout1’,检查microtask 队列,发现队列不为空,执行promise的then回调,输出’222’,microtask队列为空,进入下一个事件循环。
  6. 检查宏任务队列,发现有 setTimeout的回调函数, 立即执行回调函数输出’setTimeout2’。

再思考一下下面代码的执行顺序:

console.log('script start');

setTimeout(function () {
    console.log('setTimeout---0');
}, 0);

setTimeout(function () {
    console.log('setTimeout---200');
    setTimeout(function () {
        console.log('inner-setTimeout---0');
    });
    Promise.resolve().then(function () {
        console.log('promise5');
    });
}, 200);

Promise.resolve().then(function () {
    console.log('promise1');
}).then(function () {
    console.log('promise2');
});
Promise.resolve().then(function () {
    console.log('promise3');
});
console.log('script end');

思考一下, 运行结果是什么?

运行结果为:

script start
script end
promise1
promise3
promise2
setTimeout---0
setTimeout---200
promise5
inner-setTimeout---0

那么为什么?

我们来详细说明一下, JS引擎是如何执行这段代码的:

  1. 首先顺序执行完主进程上的同步任务,第一句和最后一句的console.log
  2. 接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中(这个任务在下一次的事件循环中执行)。
  3. 接着遇到setTimeout 200,它的作用是在 200ms 后将回调函数放到宏任务队列中(这个任务在再下一次的事件循环中执行)。
  4. 同步任务执行完之后,首先检查微任务队列, 即 microtask队列,发现此队列不为空,执行第一个promise的then回调,输出 ‘promise1’,然后执行第二个promise的then回调,输出’promise3’,由于第一个promise的.then()的返回依然是promise,所以第二个.then()会放到microtask队列继续执行,输出 ‘promise2’;
  5. 此时microtask队列为空,进入下一个事件循环, 检查宏任务队列,发现有 setTimeout的回调函数,立即执行回调函数输出 ‘setTimeout—0’,检查microtask 队列,队列为空,进入下一次事件循环.
  6. 检查宏任务队列,发现有 setTimeout的回调函数, 立即执行回调函数输出’setTimeout—200’.
  7. 接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中,检查微任务队列,即 microtask 队列,发现此队列不为空,执行promise的then回调,输出’promise5’。
  8. 此时microtask队列为空,进入下一个事件循环,检查宏任务队列,发现有 setTimeout 的回调函数,立即执行回调函数输出,输出’inner-setTimeout—0’。代码执行结束.

js-异步编程原理

异步编程原理

JavaScript 引擎负责解析,执行 JavaScript 代码,但它并不能单独运行,通常都得有一个宿主环境,一般如浏览器或 Node 服务器,前文说到的单线程是指在这些宿主环境创建单一线程,提供一种机制,调用 JavaScript 引擎完成多个 JavaScript 代码块的调度,这种机制就称为事件循环( Event Loop )。

关于事件循环流程分解如下:

宿主环境为 JavaScript 创建线程时,会创建堆 (heap) 和栈 (stack) ,堆内存储 JavaScript 对象,栈内存储执行上下文;栈内执行上下文的同步任务按序执行,执行完即退栈,而当异步任务执行时,该异步任务进入等待状态(不入栈),同时通知线程:当触发该事件时(或该异步操作响应返回时),需向消息队列插入一个事件消息;当事件触发或响应返回时,线程向消息队列插入该事件消息(包含事件及回调);当栈内同步任务执行完毕后,线程从消息队列取出一个事件消息,其对应异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被丢弃,执行完任务后退栈;当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息(next tick ,事件循环流转一次称为一次 tick )。

img

很多的队列先后按顺序执行任务就形成了 Event

异步编程实现

回调函数

优点:简单、容易理解和部署。

缺点:不利于代码的阅读和维护,各个部分之间高度耦合( Coupling ),流程会很混乱。

Promise 对象

一个 promise 可能有三种状态:等待( pending )、已完成( fulfilled )、已拒绝( rejected ) ;

img

resolve,接受一个成功值,传递给绑定的 fulfilled 回调函数中。主要工作是将当前状态变为 fulfilled 状态,同时调用绑定的 fulfilled 回调函数。

reject,接受一个失败信息,传递给绑定的 rejected 回调函数中。主要工作是将当前状态变为 rejected 状态,同时调用绑定的 rejected 回调函数。

then 方法返回一个 Promise。它有两个参数,分别为 Promise 在成功和失败情况下的回调函数。

语法:

img

img

概括来说 promise 是对异步的执行结果的描述对象。

Generator

Generator 函数是 ES6 提供的一种异步编程解决方案 ,允许函数的暂停和恢复。

异步任务的封装:

img

整个过程类似于,浏览器遇到标识符 * 之后,就明白这个函数是生成器函数,一旦遇到 yield 标识符,就会将以后的函数放入此异步函数之内,待异步返回结果后再进行执行。

更深一步,从内存上来讲:

普通函数在被调用时,JS 引擎会创建一个栈帧,在里面准备好局部变量、函数参数、临时值、代码执行的位置(也就是说这个函数的第一行对应到代码区里的第几行机器码),在当前栈帧里设置好返回位置,然后将新帧压入栈顶。待函数执行结束后,这个栈帧将被弹出栈然后销毁,返回值会被传给上一个栈帧。

当执行到 yield 语句时, Generator 的栈帧同样会被弹出栈外,但 Generator 在这里耍了个花招 —— 它在堆里保存了栈帧的引用(或拷贝)!这样当 it.next 方法被调用时, JS 引擎便不会重新创建一个栈帧,而是把堆里的栈帧直接入栈。因为栈帧里保存了函数执行所需的全部上下文以及当前执行的位置,所以当这一切都被恢复如初之时,就好像程序从原本暂停的地方继续向前执行了。

而因为每次 yield 和 it.next 都对应一次出栈和入栈,所以可以直接利用已有的栈机制,实现值的传出和传入。

js-类

ECMAScript 2015 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。

定义类

类实际上是个“特殊的函数”,就像你能够定义的函数表达式函数声明一样,类语法有两个组成部分:类表达式类声明

类声明

定义一个类的一种方法是使用一个类声明。要声明一个类,你可以使用带有class关键字的类名(这里是“Rectangle”)。

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

不会提升

函数声明类声明之间的一个重要区别是函数声明会提升,类声明不会。你首先需要声明你的类,然后访问它,否则像下面的代码会抛出一个ReferenceError

let p = new Rectangle(); 
// ReferenceError

class Rectangle {}

类表达式

一个类表达式是定义一个类的另一种方式。类表达式可以是被命名的或匿名的。赋予一个命名类表达式的名称是类的主体的本地名称。

/* 匿名类 */ 
let Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

/* 命名的类 */ 
let Rectangle = class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

注意:表达式也同样受到类声明中提到的提升问题的限制。

使用 extends 创建子类

js-正则表达式

正则表达式

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象。这些模式被用于 RegExpexectest 方法, 以及 StringmatchmatchAllreplacesearchsplit 方法。本章介绍 JavaScript 正则表达式。

创建正则

字面量

var reg = /a/; 
var r1 = /test/g;

构造函数

var reg = new RegExp("a");
var r2 = new RegExp('test', 'g');

使用正则

reg.test(str)

test() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 truefalse

  • 语法

    regexObj.test(str)
    
  • 返回值

    如果正则表达式与指定的字符串匹配 ,返回true;否则false

  • 实例

    let str = 'hello world!';
    let result = /^hello/.test(str);
    console.log(result); 
    // true
    

reg.exec(str)

exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null

如果你只是为了判断是否匹配(true或 false),可以使用 RegExp.test() 方法,或者 String.search() 方法。

  • 语法

    regexObj.exec(str)
    
  • 返回值

    如果匹配成功,exec() 方法返回一个数组,并更新正则表达式对象的属性。返回的数组将完全匹配成功的文本作为第一项,将正则括号里匹配成功的作为数组填充到后面。

    如果匹配失败,exec() 方法返回 null

  • 实例

    var re = /^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:([0-5][0-9])\:([0-5][0-9])$/;
    console.log(re.exec('19:05:30')); // ['19:05:30', '19', '05', '30']
    

str.match(reg)

match() 方法检索返回一个字符串匹配正则表达式的的结果。

  • 语法

    str.match(regexp)
    
  • 返回值

    • 如果使用g标志,则将返回与完整正则表达式匹配的所有结果,但不会返回捕获组。
    • 如果未使用g标志,则仅返回第一个完整匹配及其相关的捕获组(Array)。 在这种情况下,返回的项目将具有如下所述的其他属性。
  • 实例

    var str = 'For more information, see Chapter 3.4.5.1';
    var re = /see (chapter \d+(\.\d)*)/i;
    var found = str.match(re);
    
    console.log(found);
    
    // logs [ 'see Chapter 3.4.5.1',
    //        'Chapter 3.4.5.1',
    //        '.1',
    //        index: 22,
    //        input: 'For more information, see Chapter 3.4.5.1' ]
    
    // 'see Chapter 3.4.5.1' 是整个匹配。
    // 'Chapter 3.4.5.1' 被'(chapter \d+(\.\d)*)'捕获。
    // '.1' 是被'(\.\d)'捕获的最后一个值。
    // 'index' 属性(22) 是整个匹配从零开始的索引。
    // 'input' 属性是被解析的原始字符串。
    

str.replace(reg,newstr)

replace() 方法返回一个由替换值(replacement)替换一些或所有匹配的模式(pattern)后的新字符串。模式可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。

原字符串不会改变。

  • 语法

    一个部分或全部匹配由替代模式所取代的新的字符串。
    
  • 返回值

    如果正则表达式与指定的字符串匹配 ,返回true;否则false

  • 实例

    var str2 = " andy Wu ";
    var reg2 = /(^\s*)|(\s*$)/g;
    console.log(str2.replace(reg2,""))//"andy Wu"
    

str.search(reg)

search() 方法执行正则表达式和 String 对象之间的一个搜索匹配。

  • 语法

    str.search(regexp)
    
  • 返回值

    如果匹配成功,则 search() 返回正则表达式在字符串中首次匹配项的索引;否则,返回 -1

  • 实例

    var str = "hey JudE";
    var re = /[A-Z]/g;
    var re2 = /[.]/g;
    console.log(str.search(re)); // returns 4, which is the index of the first capital letter "J"
    console.log(str.search(re2)); // returns -1 cannot find '.' dot punctuation
    

特殊字符

g:全局

i:忽略大小写

|:或

匹配‘x’或者‘y’。

例如,/green|red/匹配“green apple”中的‘green’和“red apple”中的‘red’

+:至少一个

匹配前面一个表达式 1 次或者多次。等价于 {1,}

例如,/a+/ 会匹配 “candy” 中的 ‘a’ 和 “caaaaaaandy” 中所有的 ‘a’,但是在 “cndy” 中不会匹配任何内容。

?:0到1个

匹配前面一个表达式 0 次或者 1 次。等价于 {0,1}

例如,/e?le?/ 匹配 “angel” 中的 ‘el’、”angle” 中的 ‘le’ 以及 “oslo’ 中的 ‘l’。

如果紧跟在任何量词 *、 +、? 或 {} 的后面,将会使量词变为非贪婪(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)正好相反。例如,对 “123abc” 使用 /\d+/ 将会匹配 “123”,而使用 /\d+?/ 则只会匹配到 “1”。

还用于先行断言中,如本表的 x(?=y)x(?!y) 条目所述。

*:0到多个

匹配前一个表达式 0 次或多次。等价于 {0,}。例如,/bo*/ 会匹配 “A ghost boooooed” 中的 ‘booooo’ 和 “A bird warbled” 中的 ‘b’,但是在 “A goat grunted” 中不会匹配任何内容。

.

(小数点)默认匹配除换行符之外的任何单个字符。

例如,/.n/ 将会匹配 “nay, an apple is on the tree” 中的 ‘an’ 和 ‘on’,但是不会匹配 ‘nay’。

如果 s (“dotAll”) 标志位被设为 true,它也会匹配换行符。

{}

{n} n 是一个正整数,匹配了前面一个字符刚好出现了 n 次。 比如, /a{2}/ 不会匹配“candy”中的’a’,但是会匹配“caandy”中所有的 a,以及“caaandy”中的前两个’a’。
{n,} n是一个正整数,匹配前一个字符至少出现了n次。例如, /a{2,}/ 匹配 “aa”, “aaaa” 和 “aaaaa” 但是不匹配 “a”。
{n,m} n 和 m 都是整数。匹配前面的字符至少n次,最多m次。如果 n 或者 m 的值是0, 这个值被忽略。例如,/a{1, 3}/ 并不匹配“cndy”中的任意字符,匹配“candy”中的a,匹配“caandy”中的前两个a,也匹配“caaaaaaandy”中的前三个a。注意,当匹配”caaaaaaandy“时,匹配的值是“aaa”,即使原始的字符串中有更多的a。

()

一个整体,按照小括号的而出现顺序,分为第一部分,第二部分,..

(x) 像下面的例子展示的那样,它会匹配 ‘x’ 并且记住匹配项。其中括号被称为捕获括号。模式 /(foo) (bar) \1 \2/ 中的 ‘(foo)‘ 和 ‘(bar)‘ 匹配并记住字符串 “foo bar foo bar” 中前两个单词。模式中的 \1\2 表示第一个和第二个被捕获括号匹配的子字符串,即 foobar,匹配了原字符串中的后两个单词。注意 \1\2、…、\n 是用在正则表达式的匹配环节,详情可以参阅后文的 \n 条目。而在正则表达式的替换环节,则要使用像 $1$2、…、$n 这样的语法,例如,'bar foo'.replace(/(...) (...)/, '$2 $1')$& 表示整个用于匹配的原字符串。
(?:x) 匹配 ‘x’ 但是不记住匹配项。这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。看看这个例子 /(?:foo){1,2}/。如果表达式是 /foo{1,2}/{1,2} 将只应用于 ‘foo’ 的最后一个字符 ‘o’。如果使用非捕获括号,则 {1,2} 会应用于整个 ‘foo’ 单词。更多信息,可以参阅下文的 Using parentheses 条目.
x(?=y) 匹配’x’仅仅当’x’后面跟着’y’.这种叫做先行断言。例如,/Jack(?=Sprat)/会匹配到’Jack’仅仅当它后面跟着’Sprat’。/Jack(?=Sprat|Frost)/匹配‘Jack’仅仅当它后面跟着’Sprat’或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。
(?<=y)x 匹配’x’仅仅当’x’前面是’y’.这种叫做后行断言。例如,/(?<=Jack)Sprat/会匹配到’ Sprat ‘仅仅当它前面是’ Jack ‘。/(?<=Jack|Tom)Sprat/匹配‘ Sprat ’仅仅当它前面是’Jack’或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。
x(?!y) 仅仅当’x’后面不跟着’y’时匹配’x’,这被称为正向否定查找。例如,仅仅当这个数字后面没有跟小数点的时候,/\d+(?!.)/ 匹配一个数字。正则表达式/\d+(?!.)/.exec(“3.141”)匹配‘141’而不是‘3.141’
`(? 仅仅当’x’前面不是’y’时匹配’x’,这被称为反向否定查找。例如, 仅仅当这个数字前面没有负号的时候,/(? 匹配一个数字。 /(? 匹配到 “3”. `/(? 因为这个数字前有负号,所以没有匹配到。

[]

中元符,中括号中的内容会依次取或

[xyz\] 一个字符集合。匹配方括号中的任意字符,包括转义序列。你可以使用破折号(-)来指定一个字符范围。对于点(.)和星号(*)这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义,不过转义也是起作用的。 例如,[abcd] 和[a-d]是一样的。他们都匹配”brisket”中的‘b’,也都匹配“city”中的‘c’。/[a-z.]+/ 和/[\w.]+/与字符串“test.i.ng”匹配。
[^xyz\] 一个反向字符集。也就是说, 它匹配任何没有包含在方括号中的字符。你可以使用破折号(-)来指定一个字符范围。任何普通字符在这里都是起作用的。例如,[^abc] 和 [^a-c] 是一样的。他们匹配”brisket”中的‘r’,也匹配“chop”中的‘h’。
[\b\] 匹配一个退格(U+0008)。(不要和\b混淆了。)

\

依照下列规则匹配:

在非特殊字符之前的反斜杠表示下一个字符是特殊字符,不能按照字面理解。例如,前面没有 “" 的 “b” 通常匹配小写字母 “b”,即字符会被作为字面理解,无论它出现在哪里。但如果前面加了 “",它将不再匹配任何字符,而是表示一个字符边界

在特殊字符之前的反斜杠表示下一个字符不是特殊字符,应该按照字面理解。详情请参阅下文中的 “转义(Escaping)” 部分。

如果你想将字符串传递给 RegExp 构造函数,不要忘记在字符串字面量中反斜杠是转义字符。所以为了在模式中添加一个反斜杠,你需要在字符串字面量中转义它。/[a-z]\s/inew RegExp("[a-z]\\s", "i") 创建了相同的正则表达式:一个用于搜索后面紧跟着空白字符(\s 可看后文)并且在 a-z 范围内的任意字符的表达式。为了通过字符串字面量给 RegExp 构造函数创建包含反斜杠的表达式,你需要在字符串级别和表达式级别都对它进行转义。例如 /[a-z]:\\/inew RegExp("[a-z]:\\\\","i") 会创建相同的表达式,即匹配类似 “C:" 字符串。

\b 匹配一个词的边界。一个词的边界就是一个词不被另外一个“字”字符跟随的位置或者前面跟其他“字”字符的位置,例如在字母和空格之间。注意,匹配中不包括匹配的字边界。换句话说,一个匹配的词的边界的内容的长度是0。(不要和[\b]混淆了)使用”moon”举例: /\bm/匹配“moon”中的‘m’; /oo\b/并不匹配”moon”中的’oo’,因为’oo’被一个“字”字符’n’紧跟着。 /oon\b/匹配”moon”中的’oon’,因为’oon’是这个字符串的结束部分。这样他没有被一个“字”字符紧跟着。 /\w\b\w/将不能匹配任何字符串,因为在一个单词中间的字符永远也不可能同时满足没有“字”字符跟随和有“字”字符跟随两种情况。注意: JavaScript的正则表达式引擎将特定的字符集定义为“字”字符。不在该集合中的任何字符都被认为是一个断词。这组字符相当有限:它只包括大写和小写的罗马字母,十进制数字和下划线字符。不幸的是,重要的字符,例如“é”或“ü”,被视为断词。
\B 匹配一个非单词边界。匹配如下几种情况:字符串第一个字符为非“字”字符字符串最后一个字符为非“字”字符两个单词字符之间两个非单词字符之间空字符串例如,/\B../匹配”noonday”中的’oo’, 而/y\B../匹配”possibly yesterday”中的’yes‘
\c*X* 当X是处于A到Z之间的字符的时候,匹配字符串中的一个控制符。例如,/\cM/ 匹配字符串中的 control-M (U+000D)。
\d 匹配一个数字。``等价于[0-9]。例如, /\d/ 或者 /[0-9]/ 匹配”B2 is the suite number.”中的’2’。
\D 匹配一个非数字字符。``等价于[^0-9]。例如, /\D/ 或者 /[^0-9]/ 匹配”B2 is the suite number.”中的’B’ 。
\f 匹配一个换页符 (U+000C)。
\n 匹配一个换行符 (U+000A)。
\r 匹配一个回车符 (U+000D)。
\s 匹配一个空白字符,包括空格、制表符、换页符和换行符。等价于[ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如, /\s\w*/ 匹配”foo bar.”中的’ bar’。
\S 匹配一个非空白字符。等价于 [^ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如,/\S\w*/ 匹配”foo bar.”中的’foo’。
\t 匹配一个水平制表符 (U+0009)。
\v 匹配一个垂直制表符 (U+000B)。
\w 匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_]。例如, /\w/ 匹配 “apple,” 中的 ‘a’,”$5.28,”中的 ‘5’ 和 “3D.” 中的 ‘3’。
\W 匹配一个非单字字符。等价于 [^A-Za-z0-9_]。例如, /\W/ 或者 /[^A-Za-z0-9_]/ 匹配 “50%.” 中的 ‘%’。
\*n* 在正则表达式中,它返回最后的第n个子捕获匹配的子字符串(捕获的数目以左括号计数)。比如 /apple(,)\sorange\1/ 匹配”apple, orange, cherry, peach.”中的’apple, orange,’ 。
\0 匹配 NULL(U+0000)字符, 不要在这后面跟其它小数,因为 \0 是一个八进制转义序列。
\xhh 与代码 hh 匹配字符(两个十六进制数字)
\uhhhh 与代码 hhhh 匹配字符(四个十六进制数字)。
\u{hhhh} (仅当设置了u标志时)使用 Unicode 值 hhhh 匹配字符(十六进制数字)。

\d

0-9的数组

\w

数字字母下划线,[0-9a-zA-Z_]

\D

除了数字

\W

除了数字字母下划线

^

除了,非,但是仅限在[]中使用时,才是这个意思

开头,在[]外使用才是这个意思

匹配输入的开始。如果多行标志被设置为 true,那么也匹配换行符后紧跟的位置。

例如,/^A/ 并不会匹配 “an A” 中的 ‘A’,但是会匹配 “An E” 中的 ‘A’。

当 ‘^‘ 作为第一个字符出现在一个字符集合模式时,它将会有不同的含义。反向字符集合 一节有详细介绍和示例。

$

匹配输入的结束。如果多行标示被设置为 true,那么也匹配换行符前的位置。

例如,/t$/ 并不会匹配 “eater” 中的 ‘t’,但是会匹配 “eat” 中的 ‘t’。

贪婪匹配

需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0

var re = /^(\d+)(0*)$/;
re.exec('102300'); // ['102300', '102300', '']

由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。

必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:

var re = /^(\d+?)(0*)$/;
re.exec('102300'); // ['102300', '1023', '00']

js-Event 对象

Event 对象

​ Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。

​ 事件通常与函数结合使用,函数不会在事件发生前被执行!

​ 注意,所有事件的语法都是一样的,行为的触发方式有区别

只有在事件发生的时候,才会产生事件对象,无法手动创建,并且事件对象只能在处理函数内部访问,处理函数允许结束后该对象自动销毁

绑定方式:赋值式

元素.事件名 = function(){};
  • 事件源:元素,绑定事件的元素
  • 事件类型:onclick:触发事件的行为
  • 事件处理函数:function:触发这个事件时要做的事情

获取事件对象

事件处理函数的第一个参数:正常浏览器

var obox = document.querySelector(".box");
obox.onclick = function(eve){
    console.log(eve);
}

通过window找event属性:IE浏览器

var obox = document.querySelector(".box");
obox.onclick = function(eve){
    console.log(window.event);
}
兼容性写法
var obox = document.querySelector(".box");
obox.onclick = function(eve){
    var e = eve || window.event;
}
type

事件类型

var obox = document.querySelector(".box");
obox.onclick = function(eve){
    var e = eve || window.event;
    console.log(e.type)//click,这里显示触发事件的类型
}
target

触发事件的对象 (某个DOM元素) 的引用。当事件处理程序在事件的冒泡或捕获阶段被调用时,它与event.currentTarget不同。

var obox = document.querySelector(".box");
obox.onclick = function(eve){
    var e = eve || window.event;
    console.log(e.target)//<div class="box></div>",这里显示触发事件的对象
}
button

**mouseEvent.button**是只读属性,它返回一个值,代表用户按下并触发了事件的鼠标按键。

这个属性只能够表明在触发事件的单个或多个按键按下或释放过程中哪些按键被按下了。因此,它对判断mouseenter, mouseleave, mouseover, mouseout or mousemove这些事件并不可靠。

用户可能会改变鼠标按键的配置,因此当一个事件的**MouseEvent.button**值为0时,它可能不是由物理上设备最左边的按键触发的。但是对于标准按键布局的鼠标设备来说,这个值应该是能正确使用的。

  • 语法

    var buttonPressed = instanceOfMouseEvent.button
    
  • 返回值

    一个数值,代表按下的鼠标按键:

    0:主按键被按下,通常指鼠标左键 or the un-initialized state
    1:辅助按键被按下,通常指鼠标滚轮 or the middle button (if present)
    2:次按键被按下,通常指鼠标右键
    3:第四个按钮被按下,通常指浏览器后退按钮
    4:第五个按钮被按下,通常指浏览器的前进按钮
    对于配置为左手使用的鼠标,按键操作将正好相反。此种情况下,从右至左读取值。

  • 实例

    var whichButton = function (e) {
        // Handle different event models
        var e = e || window.event;
        var btnCode;
    
        if ('object' === typeof e) {
            btnCode = e.button;
    
            switch (btnCode) {
                case 0:
                    console.log('Left button clicked.');
                break;
    
                case 1:
                    console.log('Middle button clicked.');
                break;
    
                case 2:
                    console.log('Right button clicked.');
                break;
    
                default:
                    console.log('Unexpected code: ' + btnCode);
            }
        }
    }
    
buttons

返回对应的鼠标按键

MouseEvent.

img

鼠标相对于事件源的坐标

offsetX

MouseEvent 接口的只读属性 offsetX 规定了事件对象与目标节点的内填充边(padding edge)在 X 轴方向上的偏移量。

鼠标相对于事件源的坐标

  • 语法

    var xOffset = instanceOfMouseEvent.offsetX;
    
  • 返回值

    一个双精度浮点值。早期的规范将其规定为整数值。详见浏览器兼容性部分。
    
  • 实例

    var box = document.querySelector(".box");
    box.onclick = (eve)=>{
    var e = eve || window.event;
    console.log(e.offsetX,e.offsetY)
    console.log(e.clientX,e.clientY)
    console.log(e.pageX,e.pageY)
    console.log(e.screenX,e.screenY)
    
    }
    
offsetY

MouseEvent 接口的只读属性 offsetX 规定了事件对象与目标节点的内填充边(padding edge)在 X 轴方向上的偏移量。

鼠标相对于事件源的坐标

  • 语法

    var xOffset = instanceOfMouseEvent.offsetX;
    
  • 返回值

    一个双精度浮点值。早期的规范将其规定为整数值。详见浏览器兼容性部分。
    
  • 实例

    var box = document.querySelector(".box");
    box.onclick = (eve)=>{
    var e = eve || window.event;
    console.log(e.offsetX,e.offsetY)
    console.log(e.clientX,e.clientY)
    console.log(e.pageX,e.pageY)
    console.log(e.screenX,e.screenY)
    
    }
    

鼠标相对于页面可视区域的坐标

clientX

MouseEvent.clientX 是只读属性, 它提供事件发生时的应用客户端区域的水平坐标 (与页面坐标不同)。例如,不论页面是否有水平滚动,当你点击客户端区域的左上角时,鼠标事件的 clientX 值都将为 0 。最初这个属性被定义为长整型(long integer),如今 CSSOM 视图模块将其重新定义为双精度浮点数(double float)。

鼠标相对于页面可视区域的坐标

  • 语法

    var x = instanceOfMouseEvent.clientX
    
  • 返回值

    被 CSSOM View Module 重新定义为一个 double 类型的浮点值. 原来这个属性是被定义为一个 long 整数. 可以在 "浏览器兼容性" 那里查看详细内容.
    
  • 实例

    var box = document.querySelector(".box");
    box.onclick = (eve)=>{
    var e = eve || window.event;
    console.log(e.offsetX,e.offsetY)
    console.log(e.clientX,e.clientY)
    console.log(e.pageX,e.pageY)
    console.log(e.screenX,e.screenY)
    
    }
    
clientY

MouseEvent.clientX 是只读属性, 它提供事件发生时的应用客户端区域的Y坐标 (与页面坐标不同)。例如,不论页面是否有水平滚动,当你点击客户端区域的左上角时,鼠标事件的 clientX 值都将为 0 。最初这个属性被定义为长整型(long integer),如今 CSSOM 视图模块将其重新定义为双精度浮点数(double float)。

鼠标相对于页面可视区域的坐标

  • 语法

    var x = instanceOfMouseEvent.clientY
    
  • 返回值

    被 CSSOM View Module 重新定义为一个 double 类型的浮点值. 原来这个属性是被定义为一个 long 整数. 可以在 "浏览器兼容性" 那里查看详细内容.
    
  • 实例

    var box = document.querySelector(".box");
    box.onclick = (eve)=>{
    var e = eve || window.event;
    console.log(e.offsetX,e.offsetY)
    console.log(e.clientX,e.clientY)
    console.log(e.pageX,e.pageY)
    console.log(e.screenX,e.screenY)
    
    }
    

鼠标相对于页面的坐标

pageX

pageX 是一个由MouseEvent接口返回的相对于整个文档的x(水平)坐标以像素为单位的只读属性。

这个属性将基于文档的边缘,考虑任何页面的水平方向上的滚动。举个例子,如果页面向右滚动 200px 并出现了滚动条,这部分在窗口之外,然后鼠标点击距离窗口左边 100px 的位置,pageX 所返回的值将是 300。

起初这个属性被定义为长整型。 CSSOM 视图模块将它重新定位为双浮点数类型。请参阅浏览器兼容性部分了解详情。

鼠标相对于页面的坐标

  • 语法

    var pos = event.pageX
    
  • 实例

    var box = document.querySelector(".box");
    box.onclick = (eve)=>{
    var e = eve || window.event;
    console.log(e.offsetX,e.offsetY)
    console.log(e.clientX,e.clientY)
    console.log(e.pageX,e.pageY)
    console.log(e.screenX,e.screenY)
    
    }
    
pageY

pageY 是一个由[MouseEvent`](https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent)接口返回的相对于整个文档的Y(水平)坐标以像素为单位的只读属性。

鼠标相对于页面的坐标

  • 语法

    var pos = event.pageY
    
  • 实例

    var box = document.querySelector(".box");
    box.onclick = (eve)=>{
    var e = eve || window.event;
    console.log(e.offsetX,e.offsetY)
    console.log(e.clientX,e.clientY)
    console.log(e.pageX,e.pageY)
    console.log(e.screenX,e.screenY)
    }
    

鼠标相对于屏幕的坐标

screenX

screenX 是只读属性,他提供了鼠标相对于屏幕坐标系的水平偏移量。

鼠标相对于屏幕的坐标

  • 语法

    var pixelNumber = instanceOfMouseEvent.screenX
    
  • 返回值

    一个双浮点值。早期版本的规格定义这是一个 整数指的像素数。有关详细信息,请参见浏览器兼容性部分。
    
  • 实例

    var box = document.querySelector(".box");
    box.onclick = (eve)=>{
        var e = eve || window.event;
        console.log(e.offsetX,e.offsetY)
        console.log(e.clientX,e.clientY)
        console.log(e.pageX,e.pageY)
        console.log(e.screenX,e.screenY)
    }
    
screenY

screenY 是只读属性,他提供了鼠标相对于屏幕坐标系的垂直偏移量。

鼠标相对于屏幕的坐标

  • 语法

    var pixelNumber = instanceOfMouseEvent.screenY
    
  • 返回值

    一个双浮点值。早期版本的规格定义这是一个 整数指的像素数。有关详细信息,请参见浏览器兼容性部分。
    
  • 实例

    var box = document.querySelector(".box");
    box.onclick = (eve)=>{
    var e = eve || window.event;
    console.log(e.offsetX,e.offsetY)
    console.log(e.clientX,e.clientY)
    console.log(e.pageX,e.pageY)
    console.log(e.screenX,e.screenY)
    
    }
    

KeyboardEvent.

KeyboardEvent 对象描述了键盘的交互方式。 每个事件都描述了一个按键(Each event describes a key);事件类型keydownkeypresskeyup 可以确定是哪种事件在活动。

keyCode

返回当前按键的ASCII码

ASCII码
空格 32
回车 13
37
38
39
40
which
兼容性写法
document.onkeydown = function(eve){
        var e = eve || window.event;
        var code = e.keyCode || e.which;
    }
ctrlKey

鼠标事件ctrlKey是只读属性,可返回一个布尔值,当ctrl键被按下,返回true,否则返回false

  • 语法

    var ctrlKeyPressed = instanceOfMouseEvent.ctrlKey
    
  • 返回值

    A boolean
    
  • 实例

    document.onkeypress = ()=>{
        console.log(e.ctrlKey);/如果按下了ctrl就为true,没按下ctrl就为false
    }
    
altKey

鼠标事件altKey是只读属性,可返回一个布尔值,当alt键被按下,返回true,否则返回false

  • 语法

    var altKeyPressed = instanceOfMouseEvent.altKey
    
  • 返回值

    A boolean
    
  • 实例

    document.onkeypress = ()=>{
        console.log(e.altKey);/如果按下了alt就为true,没按下alt就为false
    }
    
shiftKey

鼠标事件shiftKey是只读属性,可返回一个布尔值,当shift键被按下,返回true,否则返回false

  • 语法

    var shiftKeyPressed = instanceOfMouseEvent.shiftKey
    
  • 返回值

    A boolean
    
  • 实例

    document.onkeypress = ()=>{
        console.log(e.shiftKey);/如果按下了shift就为true,没按下shift就为false
    }
    

事件冒泡

当内层元素的事件被触发时,会依次向上触发所有父级的相同事件,即便父级没有事件处理函数,但父级有事件,因此依然会往上冒泡

event.stopPropagation

阻止捕获和冒泡阶段中当前事件的进一步传播。

  • 语法

    event.stopPropagation(); 
    
  • 实例

    function stopBubble(e) {
        if (e.stopPropagation) {
            e.stopPropagation();
        } else {
            e.cancelBubble = true;
        }
    }
    
event.cancelBubble

获取或设置一个布尔值,表明当前事件是否要取消冒泡.

  • 语法

    event.cancelBubble = bool;
    var bool = event.cancelBubble;
    

    如果一个事件是可冒泡的,则它的cancelBubble属性的默认值为 false,代表允许该事件向上冒泡. 将cancelBubble属性设置为true以后,可以阻止该事件的进一步冒泡行为.

  • 实例

    function stopBubble(e) {
        if (e.stopPropagation) {
            e.stopPropagation();
        } else {
            e.cancelBubble = true;
        }
    }
    

阻止浏览器默认行为

Event.returnValue

Event.returnValue 属性表示该事件的默认操作是否已被阻止。默认情况下,它被设置为true,允许发生默认操作。将该属性设置为false,可以防止默认操作。

  • 语法

    event.returnValue = bool;
    var bool = event.returnValue;
    
  • 实例

    e.returnValue = false;
    
Event.preventDefault

Event 接口的 preventDefault()方法,告诉user agent:如果此事件没有被显式处理,那么它默认的动作也不要做(因为默认是要做的)。此事件还是继续传播,除非碰到事件侦听器调用stopPropagation()stopImmediatePropagation(),才停止传播。

  • 语法

    event.preventDefault();
    
  • 返回值

    undefined

  • 实例

    e.preventDefault()
    
兼容性写法
function stopDefault(e){
    if(e.preventDefault)    e.preventDefault();//兼容其他浏览器
    else    e.returnValue = false;//兼容IE
}
return false

在某一事件类型的事件处理函数结尾加上return false会阻止该事件类型的默认行为

事件流

事件触发阶段主要由于事件流:DOM0级事件处理阶段和DOM2级事件处理

DOM0级事件

​ 通过一种赋值方式,是被所有浏览器所支持的,简单易懂容易操作:元素.onclick = function(){}

DOM2级事件

​ 通过所有DOM节点中的方法,可以重复绑定,但是浏览器兼容存在问题

触发顺序
  • 事件冒泡:从里向外
  • 事件捕获:从外向内
  • 目标阶段:当前元素的当前事件

img

事件监听器

addEventListener()

EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Element,DocumentWindow或者任何其他支持事件的对象 (比如 XMLHttpRequest)

addEventListener()的工作原理是将实现EventListener的函数或对象添加到调用它的EventTarget上的指定事件类型的事件侦听器列表中。

  • 语法

    target.addEventListener(type, listener[, options]);
    target.addEventListener(type, listener[, useCapture]);
    target.addEventListener(type, listener[, useCapture, wantsUntrusted  ]);  // Gecko/Mozilla only
    
  • 返回值

    undefined

  • 实例

    var obox = document.querySelector(".box");
    obox.addEventListener("click",function(){
        console.log(1);
    })
    
add兼容性写法
function addEvent(ele,type,callback){
    if(ele.attachEvent)    ele.attachEvent("on"+type,callback);//兼容IE
    else    ele.addEventListener(type,callback);
}
removeEventListener()

删除使用 EventTarget.addEventListener() 方法添加的事件。使用事件类型,事件侦听器函数本身,以及可能影响匹配过程的各种可选择的选项的组合来标识要删除的事件侦听器。

  • 语法

    target.removeEventListener(type, listener[, options]);
    target.removeEventListener(type, listener[, useCapture]);
    
  • 实例

    var obox = document.querySelector(".box");
    function fn1(){
        console.log(1);
    }
    obox.addEventListener("click",fn1)
    obox.removeEventListener("click",fn1)
    
remove兼容性写法
function removeEvent(ele,type,callback){
    if(ele.detachEvent)    ele.detachEvent("on"+type,callback);//兼容IE
    else    ele.removeEventListener(type,callback);
}

事件委托

将多个子元素的相同事件,加给共同的现有的父元素,实现节省事件的目的

事件委托的好处4
  • 节省性能
  • 可以给页面上暂时不存在的元素绑定事件
event.target

触发事件的对象 (某个DOM元素) 的引用。当事件处理程序在事件的冒泡或捕获阶段被调用时,它与event.currentTarget不同。

  • 语法

    let theTarget = event.target
    
  • 实例

    // Make a list
    var ul = document.createElement('ul');
    document.body.appendChild(ul);
    
    var li1 = document.createElement('li');
    var li2 = document.createElement('li');
    ul.appendChild(li1);
    ul.appendChild(li2);
    
    function hide(e){
      // e.target 引用着 <li> 元素
      // 不像 e.currentTarget 引用着其父级的 <ul> 元素.
      e.target.style.visibility = 'hidden';
    }
    
    // 添加监听事件到列表,当每个 <li> 被点击的时候都会触发。
    ul.addEventListener('click', hide, false);
    
兼容写法
 otable.onclick = function(eve){
        var e = eve || window.event;
        var target = e.target || e.srcElement;//只看这行
        if(target.nodeName === "TD"){
            var ipt = document.createElement("input");
            ipt.value = target.innerHTML;
            target.innerHTML = "";
            target.appendChild(ipt);
            ipt.focus();

            var that = target;
            ipt.onblur = function(){
                that.innerHTML = this.value;
            }
            ipt.onclick = function(eve){
                var e = eve || window.event;
                e.stopPropagation();
            }
        }
    }
闭包封装写法

HTML:

<div class="box">
    <p class="red">段落1</p>
    <span>文本1</span>
    <p>段落2</p>
    <span>文本2</span>
    <p class="red">段落3</p>
    <span>文本3</span>
    <h2>二级标题1</h2>
</div>
<div class="box1">
    <p>段落1</p>
    <span class="red">文本1</span>
    <p>段落2</p>
    <span>文本2</span>
    <p class="red">段落3</p>
    <span>文本3</span>
    <h2 class="red">二级标题2</h2>
</div>

JS:

let obox = document.querySelector(".box");
    let obox1 = document.querySelector(".box1");
    let aspan = document.querySelectorAll(".box span")
    let ap = document.querySelectorAll(".box1 p")
    let ared = document.querySelectorAll(".box1 .red")
    obox.onclick = fn(aspan,function(){
        console.log(this);
        this.style.color = "red";
    });
    obox1.onclick = fn(ared,function () { 
        console.log(this);
     })

    function fn(achild,callback){
        return function(eve){
            let e = eve ||window.event;
            let target = e.target || e.srcElement;
            for(let i=0;i<achild.length;i++)
            {
                if(target == achild[i])
                {
                    callback.call(target);
                }
            }
        }

    }

鼠标事件

定义 事件名
左键单击 click
双击 dblclick
右键单击 contextmenu
按下 mousedown
移动 mousemove
抬起 mouseup
进入 mouseover
离开 mouseout
鼠标拖拽效果
  1. 按下鼠标
  2. 移动鼠标
  3. 松开鼠标
  1. 给目标元素添加onmousedown事件,拖拽的前提是在目标元素按下鼠标左键

  2. 当onmousedown发生以后,此刻给document添加onmousemove事件,意味着此刻鼠标在网页的移动都将改变目标元素的位置

  3. 在onmousemove事件中,设定目标元素的left和top

    公式:

    目标元素的left = 鼠标的clientX – (鼠标和元素的横坐标差,即offsetX)

    目标元素的top = 鼠标的clientY– (鼠标和元素的纵坐标差,即offsetY)

  4. 当onmousedown发生以后,此刻给document添加onmouseup事件,意味着此刻鼠标在网页的任意位置松开鼠标,都会放弃拖拽的效果 在onmouseup事件中,取消document的onmousemove事件即可。

弹出窗口拖拽.html

键盘事件

定义 事件名 备注
按下 keydown 一般作用于能加焦点的元素
抬起 keyup 一般作用于能加焦点的元素
敲击 keypress 一般作用于能加焦点的元素

表单事件

定义 事件名 备注
获取焦点 focus
失去焦点 blur
内容改变 change 失去焦点后触发
提交 submit 作用于form元素
重置 reset 作用于form元素
输入 input 立即触发

浏览器事件

浏览器事件 window
加载完成 load
滚动 scroll
改变大小 resize
onscroll

事件在元素滚动条在滚动时触发。

  • 语法

    window.onscroll = ()=>{
        console.log(1);
    }
    
  • 实例

    <body style="height: 5000px;">//有滚动条时才可以触发滚动事件
    </body>
    
    <script>
       
        window.onscroll = () => {
            console.log(1);
        }
    </script>
    
  • 相关变量

    1. document.documentElement.scrollTop

      获取右滚动条的位置,可以赋值给该变量,赋值后配合事件触发会改变滚动条的位置

          document.onclick = function(){
              document.documentElement.scrollTop += 100;
          }
      
    2. document.documentElement.scrollLeft

      获取下滚动条的位置,可以赋值给该变量,赋值后配合事件触发会改变滚动条的位置

          document.onclick = function(){
              document.documentElement.scrollLeft += 100;
          }
      
onresize()

onresize 事件会在窗口或框架被调整大小时发生。

  • 语法

    onresize="SomeJavaScriptCode"
    
  • 实例

    window.onresize = ()=>{
        console.log(document.documentElement.clientWidth);
        console.log(document.documentElement.clientHeight);
    }
    
  • 相关变量

    1. document.documentElement.clientWidth

      浏览器窗口宽度

    2. document.documentElement.clientHeight

      浏览器窗口高度

onload()

onload 事件会在页面或图像加载完成后立即发生。

  • 语法

    onload="SomeJavaScriptCode"
    
  • 实例

    <head>
        <script>
            // onload:页面和资源加载完成触发
            onload = function(){
                // console.log(1)
                var box = document.getElementById("box");
                console.log(box);
            }
    
        </script>
    </head>
    <body style="height: 3000px;">
        <div id="box">123</div>
    </body>
    

    按照浏览器的渲染规则,该实例的JS代码放在了body前面去执行,会造成先渲染到JS的时候找不到div元素,因此无法正常工作。

    因此,当JS代码放在body前面执行时,需要配合onload事件才能触发JS