======================================================================================================================================================
==================================================================== JavaScript ==================================================================
=☆=☆☆====== ES5 =================
*** == 新增知识点的意思
1.Screen对象 显示器对象,隶属于Window的核心属性之一
screen.availWidth: 属性,返回屏幕可用宽度
screen.availHeight: 属性,返回屏幕可用高度
screen.width: 属性,返回屏幕的像素宽度
screen.height: 属性,返回屏幕的像素高度
screen.colorDepth: 属性,返回屏幕颜色的位数
2.Location对象 位置对象,隶属于Window的核心属性之一
location.href 属性,可赋值更改,返回当前页面的URL
location.pathname 属性,可赋值更改,返回URL的路径名
location.port 属性,可赋值更改,返回当前页面的端口号
location.search 属性,可赋值更改,返回从"?"问号开始的URL
location.protocol 属性,可赋值更改,返回当前URL的协议
location.reload() 方法,无参数,重新加载本页面
location.replace("http://www.baidu.com/") 方法,带参数,用新的文档代替当前的文档
参数:URL
location.assign("http://www.baidu.com/") 方法,带参数,加载新的文档
参数:URL
***
window.location.assign(url):加载URL指定的新的 HTML 文档。 就相当于一个链接,跳转到指定的url,当前页面会转为新页面内容,可以点击后退返回上一个页面。
window.location.replace(url):通过加载URL指定的文档来替换当前文档 ,这个方法是替换当前窗口页面,前后两个页面共用一个窗口,此时若点击后退则是返回至跳
转之前的上一个页面。
3.History对象 历史对象,隶属于Window的核心属性之一
history.back() 方法,无参数,返回上一个URL,与浏览器中点击后退按钮相同
history.forwerd() 方法,无参数,返回下一个URL,与浏览器中点击前进按钮相同
4.Navigator对象 导航器对象,隶属于Window的核心属性之一
来自Navigator对象的信息具有误导性,因为navigator数据可被浏览器使用者更改,故通过此属性获得的数据不应该被用于检测浏览器版本。
navigator.appName 属性,返回浏览器的名称
navigator.appVersion 属性,返回浏览器的平台和版本信息
navigator.platform 属性,返回运行浏览器的操作系统平台
navigator.cookieEnabled 属性,返回浏览器是否启用cookie的布尔值
5.Window对话框
alert(); 警告框,按钮:【确定】,用来确保用户得到某些信息,弹出后,需要用户点击确定才能继续执行
confirm(); 确认框,按钮:【确定】【取消】,弹出后,点击确定将返回true,点击取消,将返回false
prompt(); 提示框,按钮:【确定】【取消】且带有一个【输入框】,经常用于用户进入页面前输入某个值。点击确定,返回用户输入的值;点击取消,则返回
"null"
6.Window定时器
隶属于Window的核心方法,用于操作事物触发的时间。
setTimeout(参数一,参数二) 此函数的作用为,在时间参数倒计时结束时,【执行一次】第一个参数内的javaScript语句。注意:只执行一次。
第一个参数:是含有JavaScript的语句字符串,通常为方法,或者简单的javaScript语句
第二个参数:毫秒数,1000毫秒等于1秒
clearTimeout(参数一) 取消设置的setTimeout函数,所以它的参数即为setTimeout的对象
setInterval(参数一,参数二) 此函数的作用为,每间隔一次时间参数的时间,【重复执行】第一个参数内的javaScript语句。注意:重复执行。
第一个参数:是含有JavaScript的语句字符串,通常为方法,或者简单的javaScript语句
第二个参数:毫秒数,1000毫秒等于1秒
clearInterval(参数一) 取消设置的setInterval函数,所以它的参数即为setInterval的对象
7.Cookie对象 隶属于Document的属性之一
>>.是储存在访问者计算机中的变量,每当同一台计算机通过浏览器请求某个页面时,就会发送这个cookie,我们可以使用javaScript来创建和取回cookie数据。
使用示例如下:
/*新建一个cookie*/
/*设置cookie,第一个参数:cookie名字,第二个参数:cookie的值,第三个参数:cookie保存时间*/
function setCookie(name, value, havedate) {
var d = new Date();
/*设置cookie的保质期*/
d.setDate(d.getDate() + havedate);
/*编写cookie,【特别注意:cookie中的时间(保质期)的key为:"expires"字符串】*/
document.cookie = name + "=" + value + ((havedate == null) ? "" : ";expires=" +
d.toGMTString());
}
/*查找cookie*/
/*查找指定名称的cookie,其中带有的参数,即将要查找的名称*/
function getCookie(name) {
/*判断document中是否有cookie*/
if(document.cookie.length > 0) {
/*若document中有cookie,则利用字符串的查找函数【indexOf()】查找名称*/
name_s = document.cookie.indexOf(name + "=");
/*【indexOf()】函数若是找到则返回找到位置的数字下标,若没有,则返回"-1"*/
if(name_s != -1) {
/*若含有这个名称的cookie,使用cookie分隔符";"来获取该cookie字符串的最后一个字符的下标*/
name_e = document.cookie.indexOf(";", name.length + name_s + 1);
/*判断此cookie是否是集合中的最后一个,若是,则最后没有分隔符";"*/
if(name_e == -1) name_e = document.cookie.length;
/*用获取的起始下标和末尾下标,使用字符串的截取函数【substring()】截取该cookie字符串*/
return document.cookie.substring(name_s + name.length + 1, name_e);
}
}
/*若以上步骤的某一步,判断为无,则返回""字符串*/
return "";
}
/*cookie的使用,利用提示框获取进入本页面的用户的输入(用户名),并用此用户名检查是否有该用户的cookie,若无则创建新cookie,
若有,则使用此cookie输入欢迎提示语*/
function userCookie() {
/*查找指定名称的cookie*/
username = getCookie("student");
/*判断是否有无指定的cookie*/
if(username != null && username != "") {
/*若有,提示欢迎语*/
alert("欢迎!" + username + "!");
} else {
/*若无,显示提示框,让用户输入用户名*/
username = prompt("请输入您的名字:", "");
if(username != null && username != "") {
/*判断用户是否输入,若输入,则创建cookie*/
setCookie("student", username, 1);
}
}
}
/*运行函数*/
userCookie();
以下可选的cookie属性值可以跟在键值对后,用来具体化对cookie的设定/更新,使用分号以作分隔:
;path=path (例如 '/', '/mydir') 如果没有定义,默认为当前文档位置的路径。
;domain=domain (例如 'example.com', 'subdomain.example.com') 如果没有定义,默认为当前文档位置的路径的域名部分。与早期规范相反的是,在域
名前面加 . 符将会被忽视,因为浏览器也许会拒绝设置这样的cookie。如果指定了一个域,那么子域也包含在内。
;max-age=max-age-in-seconds (例如一年为60*60*24*365)
【document.cookie="cookie3=xxx;max-age=31536000"】
;expires=date-in-GMTString-format 如果没有定义,值的格式参见Date.toUTCString()
【document.cookie="cookie1=nihao;expires=Fri, 31 Dec 2020 23:59:59 GMT"】
;secure (cookie只通过https协议传输)
cookie的值字符串可以用encodeURIComponent()来保证它不包含任何逗号、分号或空格(cookie值中禁止使用这些值).
1.数据类型
>>.5 种原始数据类型:
Undefined 类型:取值只有一个:undefined
Null 类型:取值只有一个:null
Boolean 类型:取值只有两个:true、false
Number 类型:这种类型既可以表示 32 位的整数,还可以表示 64 位的浮点数
String 类型:唯一没有固定大小的原始类型。
>>.1 种复杂的数据类型:
Object 引用类型
2.关键字:typeof
2-1、用法:typeof 变量
如果变量是 Boolean类型的 输出 boolean
如果变量是 Undefined类型的 输出 undefined
如果变量是 Number类型的 输出 number
如果变量是 String类型的 输出 string
如果变量是一种引用类型或 Null类型的 输出 object
2-2、typeof 之 Null 类型
2-2-1:"null"可以是对象不存在时的占位符,所以输出依旧是 object
2-2-1:"undefined"是由从"null"派生而来的所以,这个两个值是相等的,即:
document.write(null == undefined); //输出 "true"
document.write(null === undefined); //输出 "false"
2-3、typeof 之 Undefined 类型
var o;
console.log(typeof(o));//undefined
var o1;
console.log(o1);//undefined
console.log(o === o1);//true
console.log(o2);//报错
console.log(typeof o2);//虽然输出 undefined,但这个undefined不同于Undefined的"undefined"
注释:只有当变量已声明,但未赋值时,系统将为其赋值: "undefined"。注意区分上述示例中的 o2,"typeof o2"输出的 undefined不同于Undefined的"undefined"。
3.Boolean,Number,String 此三个原始数据类型属于伪对象。
>>.伪对象:可以使用对应 【对象/类】的方法和属性,伪对象 实际上不是对象,是值类型,由于某些原因 【不深究】,js解释器将这三个对象的方法和属性同样绑定在对应
的值类型之上,从而使得我们在编写js代码时会出现 字面值也拥有方法及属性的情况。
4.类型转换
4-1.字符串的转换
对象.toString()
>>.值得注意的是 Number在使用此方法时,可带有参数【参数 "8",代表 8进制,数据将先数据转化为 8进制形式的数字数据,然后在转化为字符串形式,参数"2",参数"16"
以此类推。默认为10进制】。
String(参数)
>>.强制转换成字符串和调用toString()方法的唯一不同之处在于,对 null和undefined值强制类型转换可以生成字符串而不引发错误。
var s1 = String(null); //"null"
var oNull = null;
var s2 = oNull.toString(); //报错!
4-2.数字的转换。
parseInt(p1,p2)
p1:指定转化的String类型数据,若无p2,则智能识别。
p2:取值:2,8,10,16,将参p1,按照p2给定的进制强制识别,并按照p2进制转化。
>>.方法首先查看位置 0处的字符,判断它是否是个有效数字;如果 不是该方法将返回 NaN,不再 继续执行其他操作。但如果该字符是有效数字该方法将查看位置 1
处的字符,进行同样的测试。这一过程 将持续到发现非有效数字的字符为止,此时 parseInt()将把该字符之前的字符串转换成数字。
parseFloat(p1)
【p1】:指定转化的String类型数据,无p2。
>>.从位置 0 开始查看每个字符,直到找到第一个 非有效的字符为止,然后把该字符之前的字符串转换成整数。不过,对于这个方法来说,第一个出现的小数点是有
效字符。如果有两个小数点,第二个小数点将被看作无效的。此外,字符串必须以十进制形式表示浮点数,不识别其他进制。
Number(pNum)
>>.函数的强制类型转换与 parseInt()和 parseFloat()方法的处理方式相似,只是它转换的是整个值,而不是部分值,如果无法整个字符串转换,将返回 NaN,如
果可以,Number()则会判断使用 parseInt()或parseFloat()进行转换。
4-3.布尔值的转换
Boolean(pBoo)
>>.当要转换的值是至少有一个字符的字符串、非 0 数字或对象时,Boolean() 函数将返回 true。如果该值是 空字符串、 数字 0、 null或undefined,它将返回
false。
5.对象2种创建方式:
/*第一种*/
var newobject = {one:"q",two:"w",three:"88"};
/*第二种*/
function newobject(q, w, e) {
this.one = q;
this.two = w;
/*匿名方式*/
/*this.three = function(x){
this.one = x;
}*/
/*正常方式*/
/*this.three = function myF(x){
this.one = x;
}*/
/*W3C方式*/
this.three = myF;
function myF(m){
this.one = m;
}
}
/*使用函数构造对象。*/
var myobject =new newobject("O","n");
document.write(myobject.one);//输出"O"
myobject.three("string");
document.write(myobject.one);//输出"string"
/*等同于下面写法*/
var myobject =new newobject();
newobject.call(myobject,"O","n");
document.write(myobject.one);//输出"O"
myobject.three("string");
document.write(myobject.one);//输出"string"
>>.我们就可以这样理解,JS先用new操作符创建了一个对象,紧接着就将这个对象作为this参数调用了函数。其实,JavaScript内部就是这么做的,而且任何函数都可以被这
样调用。
6.数组3种创建方式
/*第一种*/
var m2 = ["1", "2", "3", "4", "5"];
/*第二种*/
var m1 = new Array("q","r",6);
/*第三种*/
var m3 = new Array();
m3[0] = "q";
m3[1] = 33;
...
/*可以为数组定义长度,元素若是满了可以溢出,并且长度随之增长,若元素个数不足定义长度,空间长度不会缩减*/
var m4= new Array(6);
/*同样可以利用类似对象输出的for/in的方式输出*/
for(var x in m3) {
document.write(m3[x] + "");
}
document.write(m1.concat(m3) + ""); //函数【concat()】合并两个数组。
document.write(m3 + "");
document.write(m3.join("HHH") + ""); //函数【join()】将数组的元素组成为一个字符串。
document.write(m3.sort()); //函数【sort()】排序,不带参数可以为:字符串的首字母排序,不为数字排序。
>>.【join()】可带参数,参数为各个元素的间隔字符串。不带默认","。
>>.【sort()】可带参数,参数必须是函数,并且,函数要具有且只有2个参数,函数必须要返回值,sort根据返回的值进行排序,规则为:【大于0,交换位置】【小于0或等
于0,不交换位置】一共进行【length-1轮】重组比较,每次比较开始元素位数递进一位。
7.匿名函数的两种调用方式
>>.用被匿名函数赋值的变量名来调用;
>>.直接在赋值语句的时候,在匿名函数的后面加个小括号进行自运行调用【此时必须将匿名函数用小括号括起来】。
(function() {
var x = 8;
x2 = 10;
alert("ssss");
})();
8.switch的用法
var n = 2;
switch(n) {
case 2:
x = "Today it's Saturday";
break;
default:
x = "Looking forward to the Weekend";
}
>>."n"为变量;当所有的case的值不匹配变量n的值之后,将会执行default的内容。同时,这里的n变量可以是字符串类型的。
9.增强for循环的使用:这里使用到关键字:in。
var list = ["1", "2", "3", "4", "5", "6"]
for(var x in list) {
document.write(list[x] + "");
}
10.do/while循环注意事项。
var m = ["1", "2", "3", "4", "5"];
var n = 0;
do {
document.write(m[n] + "");
n++;
} while (n < m.length);
>>.do/while循环至少执行一次,只有当:while的条件为true时,才会继续下次循环。
11.break 和 continue 的使用。
>1.示例1:
for(var i = 0; i < 10; i++) {
if(i % 2 == 0)
continue; //跳过数字:2、4、6、8,执行下次循环
else if(i == 7)
break; //跳过数字7,并且退出循环
else
document.write("输出被循环的数字:" + i + "");
}
>2.示例2:
var iNum = 0;
for(var a = 0; a < 2; a++) {
/*标签必须加在循环体前面*/
outermost: for(var i = 1; i <= 3; i++) {
for(var j = 1; j <= 3; j++) {
if(i == 1 && j == 3)
continue outermost; //
if(i == 3 && j == 1)
break outermost;
iNum++;
}
}
alert(iNum); //第一次输出 "5",第二次输出"10"
}//由于标签在此循环内部,所有break与continue无法跳出本循环,最后将输出两次值【5,10】
>1.不带标签:break 跳出内层循环且只能跳出一个循环体,或者跳出一个switch语句。continue 跳过本次循环且只能跳出一个循环体,之后进入下一次循环。
>2.带有标签:break 跳回到标签所在的位置,不再执行标签内部的循环,可一次跳出多个循环体。
continue 跳回到标签所在的位置,跳出本次标签内部的循环,可一次跳出多个循环体。
12.try/catch的使用
dd
>>JS
function myF() {
try {
document.getElementById("ydo").innerHTML = "对了";
} catch(e) {
document.getElementById("mydo").innerHTML = "错了";
console.log(e);//"TypeError: Cannot set property 'innerHTML' of null"
}
}
myF();
>1.try与catch是配套存在的。
>2.你可以理解为,如果try内部语句出错,则执行catch内语句。
13.使用js为HTML添加事件函数的注意事项。
HHH
14.js事件
示例:
HHH
>>JS
document.getElementsByTagName("button")[0].onmousedown = function(){myF1(this)};
document.getElementsByTagName("button")[0].onmouseleave = function(){myF2(this)};
function myF1(m){
m.innerHTML = ">>>>>>>";
}
function myF2(m){
m.innerHTML = "<<<<<<<";
}
onchange 事件会在指定标签发生变化时触发。
onload 事件会在用户进入页面时被触发。
onunload 事件会在用户离开页面时被触发。
onmouseover 事件会在用户鼠标移入元素时触发。
onmouseout 事件会在用户鼠标移开元素时触发。
onmousedown 当点击鼠标按钮时,会触发。
onmouseup 当释放鼠标按钮时,会触发。
onclick 当完成鼠标点击时,会触发。
15.使用js操作HTML元素。
HHH
>>JS
/*增添一个HTML元素*/
var body = document.getElementsByTagName("body")[0];
var p = document.createElement("p");
var txt = document.createTextNode("HHH");
p.appendChild(txt);
body.appendChild(p);
/*删除一个HTML元素*/
var butt = document.getElementsByTagName("button")[0];
butt.parentNode.removeChild(butt);
>>. /*增添一个HTML元素,顺序如下:*/
>1.创建文本内容"HHH";创建标签; >2.将"HHH"文本放入
标签; >3.将
标签放入
标签;
/*删除一个HTML元素,移除标签,必须使用其父标签移除,无法直接移除标签*/
>1.找到将要删除的标签; >2.找到标签的父标签【parentNode可以返回元素的父标签】; >3.使用父标签移除子标签。
16.使用for/in遍历循环对象属性。
function newobject(q){
this.O = q;
this.C = function(w){
this.G = w;
}
}
var m = new newobject("QQ");
m.C("MM");
for(var n in m){
console.log(m[n]);//QQ、function(w){this.G = w;}、MM
console.log(n);//O、C、G
console.log(m.n)//undefined
}
/*标记1*/
console.log(m[2]);//undefined
console.log(m.G);//MM
>>.for/in形式输出的是【"m[n]"】,勿写成【"m.n"】。可知,这个"n"类似下标的数值,但一定要注意不是下标的数值,例如上例的标记1处,输出的都是undefined。
>>.观察可知,"m.C"也是m对象的属性,"m.C()"属性为m对象增添另外一个属性:"G"。注意此处this指向被创建的对象。
17.call()
语法:
Function.call(obj,param1,param2,…paramN)
>参 obj: 这个对象将代替Function类里this对象
>参 params: 是一个参数列表
示例:
function ClassA(sColor) {
this.color = sColor;
this.sayColor = function() {
console.log(this.color);
};
}
function ClassB(sColor, sName) {
ClassA.call(this, sColor);
this.name = sName;
this.sayName = function() {
console.log(this.name);
};
}
var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor(); //blue
objB.sayColor(); //red
objB.sayName(); //john
console.log(objB.hasOwnProperty("color"));//true
注释:call()它的第一个参数用来传递当前环境下的函数的this的对象,保持this的一致性,后面的参数列表将传递给调用call()的函数。
18.apply()
语法:
Function.apply(obj,args)
>参 obj: 这个对象将代替Function类里this对象
>参 args: 数组参数,必须是数组
示例:
function ClassA(sColor) {
this.color = sColor;
this.sayColor = function() {
console.log(this.color);
};
}
function ClassB(s,sColor, sName) {
ClassA.apply(this, arguments);
this.name = sName;
this.sayName = function() {
console.log(this.name);
};
}
var objA = new ClassA("blue");
var objB = new ClassB(10,"red","John");
objA.sayColor(); //blue
objB.sayColor(); //10
objB.sayName(); //John
>>.与call()类似,第一个参数用来传递当前环境下的函数的this的对象,保持this的一致性。第二个参数只能是一个数组。可以把ClassB的整个arguments对象作为第二个参
数传递给 apply()方法,但是要注意,子类在接收父类的arguments对象作为参数时,只会按照顺序一一读取对应,并不会识别参数含义。
>>.apply()有一个妙用,第二参数可以将数组默认的转换为一个参数列表【[param1,param2,param3]转化为:param1,param2,param3】
示例:JavaScript Math 对象的 Math.max() 方法 支持多个参数,将返回最大的那个数字,但是要注意,此处的max()方法参数不能使用数组
console.log(Math.max(1,23,5,6,88)); // 88
var arr = new Array(1,23,5,6,88);
console.log(Math.max(arr)); //NaN
console.log(Math.max.apply(null,arr)); //88
>>.这里的 Math.max()不支持参数使用数组,正常情况下我们就需要来拆解数组,再一个个的传递给max函数。这时,我们可以利用apply()的可以转化数组为参数列表特
性。调用的时候第一个参数给了一个null,这个是因为没有对象去调用这个方法,并且我们只需要得到结果即可。
>>.所有类似Math.max()的方法都可以使用上述方式便捷操作,例:Math.min(),Array.push()等。
》》》》》》模块之:对象/类 ===============================================================================================☆=
1.Object构造函数
>>.Object.prototype.proto === null
>>.其他的任何构造函数的prototype属性都继承于 Object构造函数的prototype属性,故其他所有对象都具有 Object 构造函数的prototype属性(该属性是一个对象)
的属性和方法。
>>.所有的对象都是通过构造函数生成的。
>>.Object构造函数的"prototype"属性拥有的部分属性和方法
>>.属性:constructor
>>对创建对象的函数的引用(指针)。对于 Object 对象,该指针指向原始的 Object() 函数。
var m = ["1", "2", "3", "4", "5"];
document.write(m.constructor);//function Array() { [native code] }
var newobject = {one:"q",two:"w",three:"88"};
document.write(newobject.constructor);//function Object() { [native code] }
>>.方法
>1.toString() 返回对象的 原始字符串 表示。对于 Object 对象,ECMA-262 没有定义这个值,所以不同的 ECMAScript实现具有不同的值。
>2.valueOf() 返回最适合该对象的原始值。对于许多对象,该方法返回的值都与 ToString() 的返回值相同。
-
=Array构造函数=
.Array.prototype.proto === Object.prototype
-
=String构造函数=
.String.prototype.proto === Object.prototype
>>.String构造函数的"prototype"属性拥有的部分属性和方法
document.write(m2.length+"");//获取字符串长度。
document.write(m2.match(".com"+""));//指定查找字符串,若找到返回此字符串,若无,返回:null。
document.write(m2.indexOf("b"+""));//查找字符串首次出现的位置,存在返回位置序号,不存在返回-1。
//指定字符串替换字符串,第1个参数为被替换字符串。
document.write(m2.replace(".com",".net"+""));
document.write(m1+"");
document.write(m2+"");
注释:上述方法不会对原字符串做任何修改。
-
=Date构造函数=
.Date.prototype.proto === Object.prototype
>>.Date构造函数的"prototype"属性拥有的部分属性和方法
var d = new Date();
document.write(d.getTime()+"");//返回从 1970 年1月1日至今的毫秒数.注意:毫秒数!右数第四位:秒
document.write(d.getDate()+"");//月份中的天,数字显示。
document.write(d.getDay()+"");//星期几,数字显示。
document.write(d.setFullYear(2008,8,8)+"");//改变日期:年,月,日;但是具体时间不会被改变。
var d = new Date();
document.write(d.toDateString()+"");//显示日期:Thu Mar 29 2018
document.write(d+"");
d.setDate(d.getDate()+5);//使用setDate()方法将改变日期。
document.write(d);//改变后的日期,若增加天数会改变月份或者年份,那么日期对象会自动完成这种转换。
注释:设置时间注意月份内置参数范围:(0~11),上述"d.setFullYear(2008,8,8)"实际上,将月份设置成 9 月了。
-
=Boolean构造函数=
.Boolean.prototype.proto === Object.prototype
-
=Function构造函数=
.Function.prototype.proto === Object.prototype
1.Function构造函数构造的对象实际上就是方法:
我们可以使用Function关键字,类似创建对象那般来创建一个函数(前面的参数代表的是函数的参数,最后一个参数,是函数的执行体/方法体):
var function_name = new Function(arg1, arg2, ..., argN, function_body)
示例写法:
var sayHi = new Function("a","b","alert(a+b);");
等价于:
示例:特殊写法(我们日常的写法):
var sayHi = function sayHi(a, b) {
alert(a+b);
}
2.特殊对象 arguments
此对象必须在在函数代码中使用。使用特殊对象 arguments,开发者无需明确指出参数名,就能访问它们。
function doAdd(q,w,e,r,t) {
document.write(doAdd.arguments[0]+""); //2
document.write(doAdd.arguments.length +"");// 5
alert(arguments[0]+arguments[4]);// 10
}
doAdd(2,"3","s",null,8);
-
=Number构造函数=
.Number.prototype.proto === Object.prototype
-
=RegExp构造函数=
.RegExp.prototype.proto === Object.prototype
-
=Error构造函数=
.Error.prototype.proto === Object.prototype
-
=Math对象=
.Math是一个对象,并不是一个构造函数,故没有prototype属性
document.write(Math.abs(7.25) + "
"); //7.25
document.write(Math.min(7.25,7.30,1,99)); //1
》》》》》》模块之:闭包 =☆===================================================
1.闭包.js垃圾回收机制
>>.和C#、Java一样JavaScript有自动垃圾回收机制,也就是说执行环境会负责管理代码执行过程中使用的内存,在开发过程中就无需考虑内存分配及无用内存的回收问题了。
JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周
期性的执行。
2.变量生命周期
>>.不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个
过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束。一旦函数结束,局部变量就没有存在必要,可以释放它们占用
的内存。但往往垃圾回收器还必须判断哪个变量有用,哪个变量没用,对于不再有用的变量打上标记,以备将来回收。常见的有两种方式:
>>1.了解.标记清除(mark and sweep)
这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如 函数中声明一个变量,垃圾回收器将其标记为"进入环境",当变量离开环境的时候(函数执行
结束)将其标记为"离开环境"。原则上讲不能够释放进入环境的变量所占的内存,它们随时可能会被调用的到。垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,
然后去掉环境中的变量以及被环境中变量所引用的变量(闭包)在这些完成之后仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了,然后垃圾回
收器相会这些带有标记的变量机器所占空间。大部分浏览器都是使用这种方式进行垃圾回收,区别在于如何标记及垃圾回收间隔而已,只有低版本IE与众不同。
>>2.了解.引用计数(reference counting)
低版本IE所使用的回收机制。在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,
当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加 1,如果该变量的值变成了另外一个,则这个值得引用次数减 1,当这个值的引用次数变为0的
时候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
3.闭包的返回值
>>.闭包的作用:就是保存自己私有的变量(也就是局部变量),通过提供的接口(方法)给外部使用,但外部不能直接访问该变量。
示例1: 示例2:
var m = function() { m = "w";
var x = 1; var n = function mm(){
var A = function() { m = "s";
x += 1; }
console.log("函数中第一个X的值:" + x); console.log(m);//"w"
}
var o = (function() {
console.log("函数中第二个X的值:" + x);
return x;
})();
console.log("函数中第三个X的值:" + o);
return A;
};
var K = m();//"函数中第二个X的值:1" "函数中第三个X的值:1"
K(); //"函数中第一个X的值:2"
var M = m();//"函数中第二个X的值:1" "函数中第三个X的值:1"
M(); //"函数中第一个X的值:2"
M(); //"函数中第一个X的值:3"
K(); //"函数中第一个X的值:3" 注释:该输出证明形成闭包,不同实例形成各自的属性数据
>>.m:是属于被赋值变量,赋值的是一个函数表达式【 m本身就是一个函数表达式,并且 要运行 m需要加 "()",否则无法从m这里获取或者得到任何东西】运行m(),并
不会 运行 A() 方法,同样,也只是给 A 赋值一个函数表达式而已,A的内部不会运行。示例2中的语句【m="s"】并并未改变全局变量m的值,因为变量n仅仅只是被赋
值,并未执行。注意到上述中的另一个被赋予函数体的变量:变量o,与变量m不同的是,方法中的变量o被赋值于一个自执行的匿名函数,所以变量 o的值不是一个方法体,
而是return x 的值,即不需要运行o(),将返回变量o的值就将为x的值。
>>.m():利用"()" 使函数表达式运行,之后才能引用其内部属性,方法。若不运行 m(),则其内部方法"A" 也将不会被声明,更无法使用"A()"来运行 A 。另外,
"return"所返回的数字或者变量将仅限返回一层。
>>.当执行完"m()"之时,被赋予给m的方法体中的语句都将被执行,按照 JavaScript的垃圾回收机制,诸如方法体中类似x这样的变量在方法结束后,都会被垃圾回收机制
收回,但是由于(闭包的使用原理):m的方法体返回的是内部的一个变量 A,而这个变量 A刚好在方法体中又使用到了变量x,因此,就算在执行【var K = m();】语句之
后,系统将会判定:后续只要执行【K();】语句都将可能会使用到变量x,因此x变量是不需要被清理的临时变量,这样便导致了只要【K();】的存在,系统就不会回收变
量x的值,并且在继续使用【K();】可以继续操作x变量。值得注意的是:若重新运行m()【var M = m();--重新运行了"m()"】,注意,此时将会生成新对象以及新属性和
新方法,并且该对象所有的对象和方法与之前对象是各自相互独立的。
4.闭包理解:
>>.所谓的闭包,就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层函数体中的临时变量。这使得只要目标对象在生存期内
始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目标对象的方法内却始终能引
用到该变量的值,而且该值只能通这种方法来访问。即使 再次调用相同的构造函数,但只会生成 新对象和方法,新的临时变量只是对应新的值,和上次那次调用的是各自独立
的!
》》》》》》模块之:内存 =☆===========================================================
1.栈和堆
1. 栈:》》》》》 先进后出,js中的原始值存储在栈中,因为大小固定。
堆:》》》》》 先进先出,一般是对象的存储,只将对象的引用(指向地址的指针)存储在栈中,真正的对象则放在堆中。
2. 浅拷贝:仅仅只是拷贝引用(指向地址的指针)而已,即建立新的指针,但依旧指向原对象。
深拷贝:建立新的指针的同时,也在堆中新建一块内存,赋值原对象的数据,并将指针指向该内存。
>1.【示例1】:
var arrayA = [1, 2, 3, 4, 5];
var arrayB = [];
arrayA.forEach(function(e) {
arrayB.push(e);
})
var str = 'hello'
arrayA.push(str);
console.log(arrayA); // [1, 2, 3, 4, 5, "abc"]
console.log(arrayB); // [1, 2, 3, 4, 5]
>>.理解Array.forEach(function(e){})的用法
参数:一个函数表达式,并且函数需带一个参数
使用:每次forEach都将获得数组的单个数据,并且,将这单个参数传递给上述函数作为参数,每次循环执行一次这个函数。
>>.上述示例,完成了深拷贝,但是一旦数组内出现对象时,此拷贝方式便无法进行数组内对象深拷贝,示例如下:
>2.【示例2】:
var arrayA = [1, 2, 3, 4, 5];
var arrayB = [];
var obj = {
name: 'unix'
}
arrayA.push(obj);
arrayA.forEach(function(element) {
arrayB.push(element);
})
arrayB[5].name = 'Alex';
console.log(JSON.stringify(arrayA)); // [1, 2, 3, 4, 5, {name:'Alex'}]
console.log(JSON.stringify(arrayB)); // [1, 2, 3, 4, 5, {name:'Alex'}]
>>.【Array.push()】方法:带参数 在集合的末尾追加参数元素
>>.【JSON.stringify()】方法:带参数 将对象转化为JSON格式类型数据
>>.此处的arrayB随着arrayA的改变而改变,显而易见,正是因为在arrayA.push(obj)的时候,仅仅只是将obj的引用(即指针)追加,所以导致当赋值给
arrayB的时候也是指针,这样,arrayA随着arrayB的改变而改变就显得司空见惯了。
>3:【示例3】:
function CopA(a,b){
for(var v in a){
if(a[v].constructor === Object){
console.log(a[v].constructor);//"function Object() { [native code] }"
console.log(typeof(a[v]));//"object" 注释:属于引用类型数据,所以是object
b[v] = {};
CopA(a[v],b[v]);
}else if(a[v].constructor === Array){
console.log(a[v].constructor);//"function Array() { [native code] }"
console.log(typeof(a[v]));//"object" 注释:属于引用类型数据,所以是object
b[v] = [];
CopA(a[v],b[v]);
}else{
b[v]=a[v];
}
}
}
var X = new Array(12,"mm",[22,"ww",{K:[6,9]}],{"H":"iii","l":{"M":22}})
var Y = new Array();
console.log(JSON.stringify(X));
CopA(X,Y);
Y[2][2].K =[9,6];
Y[1] = 11;
Y[3].l.M = "ww";
console.log(JSON.stringify(X));//[12,"mm",[22,"ww",{"K":[6,9]}],{"H":"iii","l":{"M":22}}]
console.log(JSON.stringify(Y));//[12,11,[22,"ww",{"K":[9,6]}],{"H":"iii","l":{"M":"ww"}}]
>>.上示例可以实现真正意义上的深拷贝。完成新对象的创建和赋值。
>>.【Array.push()】本身无法完成(数组内含对象时)的深拷贝,其次也无法利用它来进行内部对象 深拷贝的实现,因为它属于数组的私有方法,对象类型无
法引用,故而当循环体为对象时,便无法执行循环拷贝。
>>.这里重点注意下for/in中的赋值方式
var m = {name:"sss"};
var m2 = {};
m2[0] = m[0]; //此种赋值方式操作对象时,无效,操作数组时,有效。
console.log(JSON.stringify(m)); //{"name":"sss"}
console.log(JSON.stringify(m2)); //{}
for(var i in m){ //此种赋值方式操作对象、数组时,全部有效。
m2[i] = m[i];
alert(i); //若被循环体为对象,则i输出 为对象属性名,若为数组,则"i"为数组下标。
}
console.log(JSON.stringify(m)); //{"name":"sss"}
console.log(JSON.stringify(m2)); //{"name":"sss"}
>>.可以理解为:在【for/in】中,使用【"Array[i]"/"Object[i]"】形式:
★赋值时,无论是数组还是对象,都是将完整的个体进行单个赋值,包括属性名和属性值【数组则是:值与下标】。
★输出时,无论是数组还是对象,也就是使用时,都只是使用其属性值进行操作。
>>.存放在栈中的简单数据段,数据大小,内存空间明确的数据我们在称之为:原始数据类型,即:5种原始类型有Undefinde、Null、Boolean、Number和
String,它们是直接按值存放的,所以可以直接访问。
>>.由于有些存储的数据大小不一定,空间不一定,需要保存在堆内存中,这时,系统将此类数据的指针赋值予变量,我们称之为引用类型,即:一般情况
下,【对象,数组,函数等】使用的就是堆来存储。我们访问时,先从栈中获得该对象的地址指针,然后到该指针指向的堆内存中获取数据。
>>.浅拷贝和深拷贝 解决了不同数据因为存储的位置不同(栈和堆) 而导致的 变量的指向问题。是属于直接指向数据,还是仅仅指向指针。当数据存储在
栈中时,指向的是 直接的数据,所以在拷贝 (新变量的赋值)时,直接创建新的内存,存放新的数据,这样创建的新变量不会与父变量"藕断丝连",
如果仅仅只是赋予新变量予指针的指向形式,那么意义上两个变量依旧是同一个变量数据,其中一个改变,另一个也会随之改变。
》》》》》》模块之:this的指向及其原理 =☆===========================================================
1.this的指向
this的指向有4种情况,一般情况下,直接调用,匿名函数,构造函数,本章第2小节将会说明this关键字存在及其指向的意义。
>>1.理解:this是--包含它的函数--作为方法被调用时--所属的对象。
>>.这句话有点咬嘴,但一个多余的字也没有,定义非常准确,我们可以分3部分来理解它!
>>1、包含它的函数。>>2、作为方法被调用时。>>3、所属的对象。
示例:
function to_green(){
this.style.color="green";
}
to_green();
分析:包含this的函数是:to_green()
该函数作为方法被window调用,故所属的对象就是window,所以指向window
我们在改一下,示例:
window.load=function(){
var example=document.getElementById("id");
example.onclick=to_green;
}
这时this又是什么呢?
我们知道通过赋值操作,example对象的onclick得到to_green的方法,那么包含this的函数就是onclick,指向example对象
>>2.在任何作用域下,直接通过 函数名(...)来对函数进行调用的方式,都称为直接调用。这时函数内部的 this 指向全局对象,在浏览器中全局对象是 window。
>>3.关于匿名函数的this指向问题。
示例:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
/*标记一*/
console.log(this.name); //My Object
return(function() {
console.log(this.name); //The window
})();
}
};
object.getNameFunc();
>>.在这个上下文(执行环境)中匿名函数 并没有绑定到任何一个对象中,意味着this指向 window(除非这个上下文(执行环境)是在严格模式下执行的,而严格模式下
该this指向undefined)。
>>.暂时可以理解为:匿名函数的this指向即:window。注意区别标记一的语句不属于匿名函数体内
>>4.关于构造函数的this指向问题。
>1.不使用new指向window
var name = "window";
function Person(name) {
console.log(this.name); //输出"window",此时this指向window
this.name = name; //此时"this.name"即window的name被重新赋值。
console.log(this.name); //输出"inwe",此时this指向window,而window的name已被更改为'inwe'
}
Person('inwe');//直接调用,this指向window
>2.使用new,这里new改变了this指向,将this由window指向Person的实例对象people
var name = "window";
function Person(name) {
console.log(this.name); //输出"undefined",因为此时的this指向people
this.name = name; //赋予people变量name值
console.log(this.name); //输出"inwe",此时的people的name值被赋予
}
var people = new Person('inwe');
2.this的原理
this指的是函数运行时所在的环境。函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foo,foo()就变成在全
局环境执行?
>>.JavaScript 语言之所以有this的设计,跟内存里面的数据结构有关系
var obj = { foo:5 };
注释:上面的代码将一个对象赋值给变量obj。JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj。也就是说,
变量obj是一个地址。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。
>>.原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。内存中 foo 保存的形式:
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
结构清晰,foo属性的值保存在属性描述对象的value属性里面。但问题在于属性的值可能是一个函数。
如:
>>. var obj = { foo: function () {} };
这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。
>>. {
foo: {
[[value]]: 函数的地址
...
}
}
然后,最关键的就是:JavaScript 允许在函数体内部,引用当前环境的其他变量。由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当
前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
var f = function () { var f = function () {
console.log(x); >>>> console.log(this.x);
}; };
左侧代码中,函数体里面使用了变量x。该变量由运行环境提供。 右侧代码中,函数体里面的this.x就是指当前运行环境的x。
>>. var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 函数f在全局环境执行,this.x指向全局环境的x
f() // 1
// 在obj环境执行,this.x指向obj.x
obj.f() // 2
>>.回到开头提出的问题,obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环
境执行。
》》》》》》模块之:js原型链机制及其原理 ☆================================================
1.prototype的设计思想 - 必读
1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。这是历史上第一个比较成熟的网络浏览器,轰动一时。但是,这个版本的浏览器只能用来浏览,不具备与
访问者互动的能力。比如,如果网页上有一栏"用户名"要求填写,浏览器就无法判断访问者是否真的填写了,只有让服务器端判断。如果没有填写,服务器端就返回错误,要求
用户重新填写,这太浪费时间和服务器资源了。因此,网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。
工程师Brendan Eich负责开发这种新语言。他觉得,没必要设计得很复杂,这种语言只要能够完成一些简单操作就够了,比如判断用户有没有填写表单。1994年正是面向
对象编程(object-oriented programming)最兴盛的时期,C++是当时最流行的语言,而Java语言的1.0版即将于第二年推出,Sun公司正在大肆造势。
Brendan Eich无疑受到了影响,Javascript里面所有的数据类型都是对象(object),这一点与Java非常相似。但是,他随即就遇到了一个难题,到底要不要设计"继承"
机制呢?如果真的是一种简易的脚本语言,其实不需要有"继承"机制。但是,Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还
是设计了"继承"。但是,他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者
的入门难度。
因此,他就把new命令引入了Javascript,用来从原型对象生成一个实例对象。但是,Javascript没有"类",怎么来表示原型对象呢?这时,他想到C++和Java使用new命
令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数。
但是,用构造函数生成实例对象,有一个致命的缺点,那就是无法共享属性和方法。每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大
的资源浪费。
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在
这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成
两种,一种是本地(呈现出来的独有的一面)的,另一种是引用(隐藏起来共享的一面)的。
2."proto"属性
★ >>.所有的对象都含有属性:"__proto__",该属性指向构造函数的 "prototype" 属性
>>.特殊:class关键字的定义的类(有父类)的 "__proto__" 属性直接指向父类,class是ES6新特性提出来的声明关键字,后面章节介绍。
示例1.
function testFun(x,y){
this.a = x;
this.b = y;
}
var testObj = new testFun("aa","2");
/* testobj的"__proto__"属性指向构造函数的"prototype"属性 */
testObj.__proto__ === testFun.prototype //true
/* testFun的"__proto__"属性指向Function的"prototype"属性 */
testFun.__proto__ === Function.prototype //true
示例2.所有的内置构造函数,包括Function的"__proto__"属性,都指向Function函数的"prototype"属性
var m = [];
m.__proto__ === Array.prototype //true
Array.__proto__ === Function.prototype //true
var n = {};
n.__proto__ === Object.prototype //true
Object.__proto__ === Function.prototype //true
Function.__proto__ === Function.prototype //ture
>>.意味着所有的内置构造函数包括其本身Function构造函数:实际上都是由Function该构造函数构造出来的
3."prototype"属性
★ >>.所有的函数都含有属性:"prototype",对象由函数生成,生成对象时,对象的"__proto__"属性指向函数的"prototype"属性。
>>.函数的 "prototype" 属性是一个对象(只要是一个对象,就具有"__proto__"属性,指向该对象的构造函数的"prototype"属性),对象两个属性:
constructor 指向本身
__proto__ 指向该对象的构造函数的 "prototype" 属性
示例1的函数(testFun):
testFun.prototype.constructor === testFun //true
testFun.prototype.__proto__ === Object.prototype //true,意味着,该属性是一个由Object构造的对象
>>.对象无法再构造新的实例,没有什么需要别人共享的,所以也就不需要,也不存在 "prototype" 属性,因为该属性是用来存放(隐藏起来共享的一面)的属性。
4.原型链
★ >>.函数可以实例化对象,而这些实例化的对象需要有共享的一面,而这一面隐藏在"prototype"属性上,函数本身也是一个对象,所以同样拥有"__proto__"属性
>>.对象.__proto__ === 构造函数.prototype
示例:
function Fun(){}
var f = new Fun();
/* 函数的prototype属性是一个对象,该对象拥有两个属性:constructor 和 __proto__ */
Fun.prototype
/*输出: {constructor: ƒ}
↲constructor: ƒ Fun() >>.constructor指向 本身
↲__proto__: Object(类型) >>. __proto__指向 Object.prototype,说明该对象由Object构造函数构造 */
typeof Fun.prototype // "Object"
Fun.prototype.__proto__ === Object.prototype // true
/* 对象的 __proto__ 属性指向其构造函数的 prototype 属性 */
两层链: f.__proto__ === Fun.prototype
Fun.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
>>.因为对象(f)上层即函数(Fun),再上层即顶层函数(Function),再上层即最顶层的(依旧是Function,意味着所有的内置构造函数包括其本身
Function构造函数:实际上都是由Function该构造函数构造出来的)
Fun.prototype.__proto__ === Object.prototype
>>.通过原型链可以使用链上级的定义在"prototype"属性中的方法,注意该方式可以动态添加。从而动态地扩展基类的功能特性。这在静态对象语言中是很不可思议的。
示例:
Object.prototype.OOO = "OOOOO"
Fun.prototype.QQQ = "QQQQQ"
f.OOO // "OOOOO"
f.QQQ // "QQQQQ"
Fun.OOO // "OOOOO"
5.源头
>>.一切的对象都是Object构造函数的子孙对象,Object 是一切(呈现出来的独有的一面)的源头,而Object.prototype 则是一切(隐藏起来共享的一面)的源头
Function.prototype.__proto__ === Object.prototype //true
Object.prototype.__proto__ === null //true,JavaScript原型链的终点
=☆=☆☆====== ES6 =================
☆代表难点 ★代表重点 ↲代表换行输出 *了解即可
》》》》》》模块之:let/const =☆===========
1.let关键字 它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
2.const关键字 一旦声明变量,就必须立即初始化,不能留到以后赋值。并且,const声明的变量不得改变值,即常量
>>.const变量的判定是基于变量的内存地址。对于复合类型的数据(主要是对象和数组),const只能保证这个内存地址是固定,数据是无法保证的。
额外:如果想要一个复合类型数据包括地址,数据都无法改变,参阅 Object.freeze() 方法
-
let/const 关键字
1.一旦let/const在某个作用域申明一个变量,那么在此之前则无法使用该变量,并且,let/const 不允许重复声明同一个变量
2.与var关键字不同,let/const申明的变量不具有变量提升的能力,因此,在使用 let/const 声明的变量之前调用该变量系统将会抛错,使用"typeof"关键字来判断该变量
也将会报错
3.在顶层作用域下,let/const 以及 class 定义的全局变量不再属于定顶层对象,而为了保持兼容性, var/function 关键字定义的全局变量依旧属于顶层对象,ES6之后,
全局变量将逐步与顶层对象的属性脱钩
4.ES6 一共有 6 种声明变量的方法:var 、 function() 、 let 、 const 、 import 、 class
》》》》》》模块之:解构赋值 ===☆=
1.数组的解构赋值
1.1.数组形式的解构赋值
let [foo, [[bar], baz]] = [1, [[2], 3]];// foo = 1、bar = 2、baz = 3
let [ , , third] = ["foo", "bar", "baz"];// third = "baz"
>>.只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值
2.1.指定默认值。
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = [null, undefined]; // x=null, y='b'
注意:ES6 内部使用严格相等运算符(===)判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。
3.1.如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() {console.log('aaa');}
let [x = f()] = [1];
注意:上面代码中,因为x能取到值,所以函数f根本不会执行。
2.对象的解构赋值
2.1.对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
let { f: b } = { f: "aaa", z: "bbb" };
>>b // "aaa"
>>f // error: f is not defined
>>.f是匹配的"模式",b才是变量。真正被赋值的是变量,而不是"模式"。对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的
是后者,而不是前者。
let { loc, loc: { start }, loc: { start: { line }}} ={loc: { start: { line: 1, column: 5}}}
>>loc // {start: {line: 1, column: 5}
>>start // {line: 1, column: 5}
>>line // 1
>>."loc,"代表:"loc:loc"
2.2.设置默认值
注释:对象的解构也可以指定默认值,默认值生效的条件是,对象的属性值严格等于undefined。如果解构失败,变量的值等于undefined。
var {x = 3} = {x: undefined};
>>x // 3
3.函数参数的解构赋值
3.1.函数的参数也可以使用解构赋值
function add([x, y = 8, z]){
return x + y + z;
}
add([1, 2, 3]); // 6
add([1,,1]);// 10
>>.函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。
>>.上述参数【"y = 8"】为设置函数参数默认值
*3.2.参数指定默认值
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
>>.注意与3.1示例区分,上述代码为函数move的参数指定默认值,而不是为变量x和y指定默认值。undefined就会触发函数参数的默认值。
>>.在调用该函数时,用入参来替换【参数括号中的"="号的后半部分,即示例的:后半部分为"{ x: 0, y: 0 }"】。
4.其他的解构赋值
4.1.利用对象的解构赋值可以快速赋值
let { log, sin, cos } = Math;// 上面代码将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上。
4.2.由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。此时,数组的下标相当于对象的属性名。
4.3.字符串也可以解构赋值,因为字符串默认具有 Iterator 接口。这是因为此时,字符串被转换成了一个类似数组的对象。
4.4.将解构赋值用来提取 JSON 数据,尤其有用。
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);// 42, "OK", [867, 5309]
*!0.记录Array.map(function callback(currentValue,index,array),thisArg) 的使用方法
Array.map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
callback(currentValue,index,array) 生成新数组元素的函数,使用三个参数:
currentValue: 数组中正在处理的当前元素
index: 数组中正在处理的当前元素的索引
array: 原数组
thisArg 执行 callback 函数时使用的 this 值。
返回:一个数组,数组的成员为每次循环的返回值
>>>.
var m = [[1,2,3],[4,5]].map(([a, b],c) => {
console.log(a); // 先后输出:1,4
console.log(b); // 先后输出:2,5
console.log(c); // 先后输出:0,1 第二个参数,为当前的下标值
return a+b; // 最后赋予m的值:[3,9]
});
var m =[1, undefined, 3].map((x = 'yes') => x);
m // [ 1, 'yes', 3 ]
》》》》》》模块之:字符串、数值、函数、数组的扩展 ☆========
1.字符串的扩展
1.1.新增的String方法
>1.比较方法,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 三种新方法。
>1.includes(参1,参2) 返回布尔值,表示是否找到了参数字符串。
>2.startsWith(参1,参2) 返回布尔值,表示参数字符串是否在原字符串的头部。
>3.endsWith(参1,参2) 返回布尔值,表示参数字符串是否在原字符串的尾部。
>2.其他方法
>1.repeat(参1) repeat方法返回一个新字符串,表示将原字符串重复n次。
>2.padStart(参1,参2) 如果某个字符串不够指定长度,会在头部补全。
>3.padEnd(参1,参2) 如果某个字符串不够指定长度,会在尾部补全。
2.1.新增字符串模板
>1.模板字符串(template string)是增强版的字符串,用反引号(`)标识。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
`In JavaScript this is
not legal.`
>2.模板字符串中嵌入变量,需要将变量名写在${}之中,大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,引用对象属性以及调用函数。
function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
`User ${user.name} is not authorized to do ${action}.`);
}
}
2.数值的扩展
2.1.ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
2.2.ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
>1.Number.parseInt()
>2.Number.parseFloat()
2.3.ES2016 新增了一个指数运算符(**)。
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。
2 ** 3 ** 2 // 相当于 2 ** (3 ** 2) 输出: 512
2.4.新增方法
Number.isNaN() Number.isNaN()用来检查一个值是否为NaN。
Number.isFinite() Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。
Number.isInteger() 用来判断一个数值是否为整数。(如果数值的范围不在:-2^53到2^53之间(不含两个端点))
3.函数的扩展
3.1.函数参数默认值的注意点
>1.ES6 允许为函数的参数设置默认值如果非尾部的参数设置默认值,则这个参数是没法省略的。除非显式输入undefined。
>2.参数变量是默认声明的,所以不能用let或const再次声明。
function foo(x = 5) {let x = 1;}// 报错
>3.指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。
>4.一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。在不设置参数默认值时,是不会出现的。
3.2.rest 参数
function add(...values) {/* 不指定参数个数的累加求和 */}
>>.ES6 引入 rest 参数形式为(...变量名),用于获取函数的多余参数。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
3.3.从 ES5 开始,函数内部可以设定为严格模式。
function doSomething(a, b) {
'use strict';
// code
}
>>.ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
>>.两种方法可以规避这种限制。第一种是设定全局性的严格模式。第二种是把函数包在一个无参数的立即执行函数里面。
3.4.新增函数属性 Function.name 作用>>>函数的name属性,返回该函数的函数名。
3.5.箭头函数
>1.ES6 允许使用"箭头"定义函数 ()=>{}。
var f = function (v) {return v;} >>>> var f = v => v;
>>.如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
>2.由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
let getTempItem = id => { id: id, name: "Temp" }; // 报错
let getTempItem = id => ({ id: id, name: "Temp" }); // 不报错
>3.箭头函数有几个使用注意点
>>1.函数体内的this对象,指向最近的上下文作用域中。
>>2.不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
>>3.不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
★>4. 箭头函数的this指向
>>.箭头函数本身是没有this的,从而导致=>>>>>指向最近的上下文作用域中。
示例1:
function M(){
this.a = 0;
setInterval(() => {
console.log(this);// M {a:0}
}, 1000);
}
var m = new M();
>>.因为箭头函数内部没有this,所以该this并不会随着setInterval()函数的特性而使this指向顶级对象,而是指向外层作用域的M函数。
示例2:
var m = 1;
var M = {
m: 2,
methods: {
m: 3,
c: function () { console.log(this.m) }, // 注意区分该this并不是在匿名函数中,所以指向M的methods属性
c2: () => { console.log(this.m) } // 对象体内部不构成作用域,所以指向最近的作用域就是外层 window 作用域
}
}
M.methods.c(); // 3
M.methods.c2(); // 1
>>.由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。除了this,以下三个变量在箭头函数之中也是不
存在的,指向外层函数的对应变量:arguments、super、new.target。
>>.由于箭头函数使得this从"动态"变成"静态",当定义函数的方法,且该方法内部包括this不应该使用箭头函数。动态this的时候,也不应使用箭头函数。(个人
理解:箭头函数在定义的时候已经固定好其内部this指向,不会再根据环境的变化而变化。所以称之为:"动态"变成"静态",参考react中的箭头函数不需要绑
定,即可直接匹配的场景)
示例:
var m = "sssssssssssssss" var l = {
"c" : a,
var a = () => { "d" : b,
console.info(this.m); "m" : "hhhhhhhhhhhhhh",
} };
var b = function(){ l.c() //ssssssssssssss,a是箭头函数,在声明的时候this的指向就已经固定
console.info(this.m) l.d() //hhhhhhhhhhhhhh
}
4.数组的扩展
4.1.扩展运算符 "..."
>1.使用:将一个数组转为用逗号分隔的参数序列,它好比 rest 参数的逆运算。该方法只能解析一层数组嵌套。
console.log(1, ...[2, 3, 4], 5); // 1 2 3 4 5
>2.扩展运算符的应用
>>>.
Math.max.apply(null, [14, 3, 77]);// ES5 的写法
Math.max(...[14, 3, 77]);// ES6 的写法
>>>.复制数组
const a1 = [1, 2];
const a2 = [...a1];// 写法一
const [...a2] = a1;// 写法二
>>>.字符串转数组
const m = [...'hello']// [ "h", "e", "l", "l", "o" ]
4.2.新增方法
>1.Array.of() 用于将一组值,转换为数组。Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
Array.of(1) // [1]
区别于:
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
>>.Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个
时,实际上是指定数组的长度。
>2.Array.prototype.flat(depth) 用于将嵌套的数组"拉平",变成一维的数组。
语法:
var newArray = arr.flat(depth)
返回:该方法返回一个新数组,对原数据没有影响。
参数: depth:'拉平'的层数,默认为1层。原数组有空位,flat()方法会跳过空位。
>>>.
[1, 2, [3, [4, 5]]].flat();// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2);// [1, 2, 3, 4, 5]
4.3.新增方法 >>>> 查询库 >> 1.
》》》》》》模块之:对象的扩展 ================================================☆
1.新的表达式
1.1.简洁表示法
>>.直接写入变量和函数,作为对象的属性和方法。
>>>.
let birth = ‘2000/01/01’;
const Person = {
birth, //等同于birth: birth
hello() { console.log(‘我的名字是’, this.name); } // 等同于hello: function ()…
};
1.2.属性名表达式
>>.ES6 允许 表达式 放在方括号内,作为对象的变量名。"[ ]"中可放入 可执行的表达式,该表达式计算的最终值作为返回。
>>>.
let propKey = ‘foo’;
let obj = {
[propKey]: true,
[‘a’ + ‘bc’]: 123,
“sss”:“SsS”
};
console.log(obj); //{foo: true, abc: 123, sss: “SsS”}
console.log(obj[‘ss’+‘s’]); //SsS
1.3.简洁表达式 和 属性名表达式 不能同时使用。
2.新增属性 name属性
>2.1.函数以及对象的方法都拥有该属性,返回函数名。
示例:
const person = {
sayName() {},
};
person.sayName.name // “sayName”
*3.属性的可枚举性 及 遍历
*3.1.方法: Object.getOwnPropertyDescriptor() 获取对象属性的描述对象
>>>.
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {value: 123, writable: true, enumerable: true, configurable: true}
var m = [1,2,3,4];
Object.getOwnPropertyDescriptor(m, 'length')
// {value: 4, writable: true, enumerable: false, configurable: false}
>>.其中,enumerable属性,称为"可枚举性",如果某个属性的该属性为false,那么以下这些操作在执行时将会忽略该属性:
>>>>
>1.for...in循环: 遍历可枚举的属性。只有for...in会处理继承的属性,下列三个方法都会忽略继承的属性,只处理对象自身的属性。
>2.Object.keys(): 返回对象自身的所有可枚举的属性的键名。
>3.JSON.stringify(): 只串行化对象自身的可枚举的属性。
>4.Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
>>."可枚举"(enumerable)这个概念的最初目的,是让某些属性可以规避掉for...in操作,比如,对象原型的toString方法,以及数组的length属性
>>.ES6 规定,所有 Class 的原型的方法都是不可枚举的。
*3.2.属性的遍历
>1.for...in >>> for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
>2.Object.keys(obj) >>> 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
>3.Object.getOwnPropertyNames(obj) >>> 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
>4.Object.getOwnPropertySymbols(obj) >>> 返回一个数组,包含对象自身的所有 Symbol 属性的键名。
>5.Reflect.ownKeys(obj) >>> 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
>>.以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则:
>>> 首先遍历所有数值键,按照数值升序排列。
>>> 其次遍历所有字符串键,按照加入时间升序排列。
>>> 最后遍历所有 Symbol 键,按照加入时间升序排列。
4.super 关键字 this关键字总是指向函数所在的当前对象,而关键字super,指向当前对象的原型对象(根据语境,该原型对象描述的是对象的"proto"的属性对象)。
super关键字只能用在对象的方法之中,用在其他地方都会报错。
5.对象的新增方法
5.1.操作对象原型
>1.Object.setPrototypeOf(obj, prototype) 用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。
>2.Object.getPrototypeOf(object) 返回其原型的对象
>3.Object.create(prototype , propertiesObject ) 创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
>>prototype 新创建对象的原型对象(使用现有的对象来提供新创建的对象的__proto__)。
>>propertiesObject 可选。要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称
>>>.该示例建议移至控制台执行,并查看输出语句,便于理解
var S = function(){ m // Function {color: "red"}
this.s = 'sSs' m.color // "red"
} m.desc = 'aaa';
S.prototype = { m.color // "AAA"
getInfo: function() {return 'S' + this.color;}
}; objs.__proto__ // {getInfo: ƒ}
m.__proto__ //ƒ (){ this.s = 'sSs' }
var objs = new S();
var m = Object.create(S, {
color: { writable: true, configurable:true, value: 'red' },
desc: {
configurable:true,
get: function () { return this.color.toUpperCase(); },
set: function (value) { this.color = value.toUpperCase(); }
}
});
5.2.改变对象属性以及属性描述符
>1.Object.defineProperties(obj, props) 在一个对象上定义新的属性或修改现有属性,并返回该对象。
>2.Object.defineProperty(obj, prop, descriptor) 在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
>>obj 属性的对象
>>prop 要定义或修改的属性的名称
>>descriptor 将被定义或修改的属性描述符
>>>.
var obj = {test:"hello"} Object.defineProperties(obj, {
Object.defineProperty(obj,'num',{ 'test': {
configurable:false, value: 'olleh',
enumerable:false, writable: true
value:555, }}
writable:true );
}); // obj {test: "olleh", num: 555}
5.3.Object.is() 在所有环境中,只要两个值是一样的,它们就应该相等。
>>.它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
5.4.Object.assign(target, ...sources)
>>.用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个
引用。继承属性和不可枚举属性是不能拷贝的。
5.5.Object.getOwnPropertyDescriptors(object) 所指定对象的所有自身属性的描述符,如果没有任何自身属性,则返回空对象。
6.对象的扩展运算符
>>.对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会
拷贝到新对象上面。
示例:
var m = {"s":"ssssssss" , "a":{"aa":"AAA","bb":"OO"} , "b":["1","2"] };
var n = {...m,q: 22}
m //{s: "ssssssss", a: {…}, b: Array(2)}
n //{s: "ssssssss", a: {…}, b: Array(2), q: 22}
>>.解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
>>.解构赋值的一个用处,作为函数的可变参数。
示例:
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
》》》》》》模块之:Symbol ========================================☆
-
Symbol 英[ˈsɪmbl] 美[ˈsɪmbl] >>> 新的数据类型(JavaScript 语言的第七种数据类型),表示独一无二的值。
.ES5 的对象属性名都是字符串,这容易造成属性名的冲突。使用一个他人提供的对象时,若需要为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。
此时就可以使用 Symbol函数 来为新函数取名。Symbol 值通过Symbol函数生成。
语法 >>> let s = Symbol();
typeof s; // "symbol"
★:Symbol函数前不能使用new命令,否则会报错。因为生成的 Symbol 是一个原始类型的值,类似于字符串的数据类型,不是对象。当然,也无法为Symbol值添加属性。
1.1.可以为Symbol函数添加参数,Symbol函数的参数只是表示对当前 Symbol 值的描述,即使是相同参数的Symbol函数所生成的Symbol值也一样是各自独一无二的。
1.2.Symbol 值不能与其他类型的值进行运算,会报错。Symbol 值也可以转为布尔值,但是不能转为数值。
1.3.Symbol.for()
>>.接受字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。若有,就返回该 Symbol 值,否则新建并返回以该字符串为名称的Symbol值
1.4.使用
var m = Symbol("f"); ==>>> n[m] //3333
var n = {} n[Symbol("f")] //undefined 因为每次该值都是不一样的
n[m]= 3333
var m = Symbol("f"); ==>>> n[Symbol.for("f")] //3333
var n = {}
n[m] = 3333
>>.Symbol.for()与 Symbol() 这两种写法,都会生成新的 Symbol。它们的区别是:前者会被登记在全局环境中供搜索,后者不会。
>>.Symbol.for()为 Symbol 值登记的名字,是全局环境的
2.作为属性名的 Symbol
2.1.★:Symbol 值作为对象属性名时,不能用点运算符,Symbol 值必须放在方括号之中。
>>>.
let s = Symbol();
let obj = {
[s]: function (arg) { ... }
};
obj[s](123); // 如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个 Symbol 值。
2.2.Symbol 的遍历 >>>> 该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回
方法 Object.getOwnPropertySymbols() 返回一个数组,成员是给定对象自身上找到的所有 Symbol 属性,并不包含该Symbol 属性的值。
>>>.
const obj = {};
let a = Symbol('aaa');
obj[a] = 'sss';
const objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols); // [Symbol(aaa)]
2.3.Symbol.keyFor() 返回一个已登记的 Symbol 类型值的key。
>>>.
var m = Symbol.for("foo"); // 创建一个 symbol 并放入 Symbol 注册表,key 为 "foo",Symbol()创建的 symbol不放入注册表中
Symbol.keyFor(m); // "foo"
3.内置的Symbol值 ES6 提供了 11 个内置的 Symbol 值,指向语言内部使用的方法 >>>> 查询库 >> 2.
》》》》》》模块之:Iterator 接口-机制 ========================================================☆
-
Iterator 接口-机制
概念:JavaScript 原有的表示"集合"的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,
定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。Iterator就是这样的一种接口机制,不
同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
*Iterator 的遍历过程:
★(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。该对象是个方法。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
.每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性
是一个布尔值,表示遍历是否结束。
.遍历器生成函数
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
console.log(nextIndex);
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
var it = makeIterator(['a', 'b']);
it.next() // 0{ value: "a", done: false }
it.next() // 1{ value: "b", done: false }
it.next() // 2{ value: undefined, done: true }
.上面代码定义了一个遍历器生成函数makeIterator(),作用就是返回一个遍历器对象。对数组[‘a’,‘b’]执行这个函数,会返回该数组的遍历器对象(即指针对象)it。
2.默认iterator接口
2.1.
>>.一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是"可遍历的"(iterable)。默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者
说,一个数据结构只要具有Symbol.iterator属性,就可以认为是"可遍历的"(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器
生成函数。
>>.ES6 的有些数据结构原生具备 Iterator 接口(比如数组),这些数据结构原生部署了Symbol.iterator属性,另外一些数据结构没有(比如对象)。凡是部署了
Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
>>.原生具备 Iterator 接口的数据结构的有:Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象
>>>.
let arr = ['a', 'b'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: undefined, done: true }
>>.上面代码中,变量arr是一个数组,原生就具有遍历器接口,部署在arr的Symbol.iterator属性上面。调用这个属性,就得到遍历器对象。对于原生部署 Iterator
接口的数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在
Symbol.iterator属性上面部署,这样才会被for...of循环遍历
>>>.
var n = {} ===>> n[Symbol.iterator]//undefined
var k = [] ===>> k[Symbol.iterator]//ƒ values() { [native code] }
2.2.在对数组和 Set 结构进行解构赋值时,使用扩展运算符(...)以及 使用yield*后面跟的是一个可遍历的结构,以及使用for...of和Array.from()等方法时会默认调用
Iterator 接口(即Symbol.iterator方法)
2.3.字符串的 Iterator 接口,字符串是一个类似数组的对象,也原生具有 Iterator 接口。
3.遍历器的return()和throw()。
>>.遍历器对象除了具有next方法,还可以具有 return()方法 和 throw()方法。如果你自己写遍历器对象生成函数,那么next()是必须部署的,return()和throw()是否部
署是可选的。
》》》》》》模块之:for…of 循环 ===========================================☆=
-
for…of循环
概念:一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for…of循环遍历它的成员。也就是说,for…of循环内部调用的是数据结
构的Symbol.iterator方法。提供了遍历所有数据结构的统一操作接口。
2.数组
>>.数组原生具备iterator接口(即默认部署了Symbol.iterator属性),JavaScript 原有的for...in循环只能获得对象的键名,不能直接获取键值。ES6 提供for...of循
环,允许遍历获得键值。
>>>.for...in循环读取键名,for...of循环读取键值
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {console.log(a);} // 0↲ 1↲ 2↲ 3↲
for (let a of arr) {console.log(a);} // a↲ b↲ c↲ d↲
>>.for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样。
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {console.log(i);} // "0"↲ "1"↲ "2"↲ "foo"↲ 使用for...in遍历数组不稳定
for (let i of arr) {console.log(i);} // "3"↲ "5"↲ "7"↲
2-1.for...of/for...in区别
>1.数组的键名是数字,但是for...in循环是以字符串作为键名"0"、"1"、"2"等等。for...in循环主要是为遍历对象而设计的,不适用于遍历数组。
>2.for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。某些情况下,for...in循环会以任意顺序遍历键名。
>3.不同于forEach方法,for...of可以与break、continue和return配合使用。forEach循环无法中途跳出,break命令或return命令都不能奏效。
>4.提供了遍历所有数据结构的统一操作接口。
3.Set 和 Map 结构 Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for…of循环。
示例:
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines)console.log(e);// Gecko↲ Trident↲ Webkit↲
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6)console.log(name + ": " + value);// edition: 6↲ committee: TC39↲ standard: ECMA-262↲
>>.Set和Map遍历的顺序是按照各个成员被添加进数据结构的顺序。Set 结构遍历返回的是一个值,Map 结构遍历返回的是一个数组,该数组的两个成员分别为当前Map
成员的键名和键值。
》》》》》》模块之:Set 和 Map ================================================☆
-
★ Set 类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成 Set 数据结构。
>>>> const s = new Set();
1.Set函数可以接受一个部署 iterable 接口的其他数据结构作为参数,用来初始化。(例如,数组,字符串等)
2.向 Set 加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值。它内部的判断两个值得算法类似于精确相等运算符(===),主要的区别是NaN等于自身,而精确
相等运算符认为NaN不等于自身。
3.Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
1.1.属性和方法
1.1.1.属性
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
1.1.2.方法
操作方法
>> Set.prototype.add(value): 添加某个值,返回 Set 结构本身。
>> Set.prototype.delete(value): 删除某个值,返回一个布尔值,表示删除是否成功。
>> Set.prototype.has(value): 返回一个布尔值,表示该值是否为Set的成员。
>> Set.prototype.clear(): 清除所有成员,没有返回值。
遍历方法
>> Set.prototype.keys(): 返回键名的遍历器
>> Set.prototype.values(): 返回键值的遍历器,Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。
Set.prototype[Symbol.iterator] === Set.prototype.values === Set.prototype.keys // true
>> Set.prototype.entries(): 返回键值对的遍历器
>> Set.prototype.forEach(): 使用回调函数遍历每个成员
>>.keys方法、values方法、entries方法返回的都是遍历器对象(默认遍历器生成函数就是它的values方法)。由于 Set 结构没有键名,只有键值(或者说键名和
键值是同一个值),所以keys方法和values方法的行为完全一致。
>>>.
let set = new Set(['red', 'green', 'blue']);
for (let item of set.values()) {
console.log(item); //↲ red //↲ green //↲ blue
}
for (let item of set.entries()) {
console.log(item); //↲ ["red", "red"] //↲ ["green", "green"] //↲ ["blue", "blue"]
} // entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相同
>>Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
>>>.
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value)) //↲ 1 : 1 //↲ 4 : 4 //↲ 9 : 9
>>.该回调函数的参数与数组的forEach一致,依次为键值、键名、集合本身(上例省略了该参数,Set的键值和键名相同)
>>.forEach方法还可以有第二个参数,表示绑定处理函数内部的this对象。
-
★ Map 类似于对象,也是键值对的集合,但是"键"的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
>>>> const m = new Map();
>1.对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。而Map 结构提供了"值—值"的对应,是一
种更完善的 Hash 结构实现。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content');
m.get(o) // "content"
>2.Map构造函数接受任何部署 Iterator 接口且每个成员都是一个双元素的数组的数据结构 作为 Map 构造函数的参数。所以,Set和Map都可以用来生成新的 Map。
>3.只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。另外,Map 的遍历顺序就是插入顺序。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
>>.上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get方法无法读取该键,返回undefined。
2.1.属性和方法
2.1.1.属性 Map.size size属性返回 Map 结构的成员总数。
2.1.2.方法
操作方法
>> Map.prototype.set(key, value) 设置键名key对应的键值为value,然后返回整个 Map 结构。若已有该key,则更新键值,否则就新生成该键。
>> Map.prototype.get(key) 读取key对应的键值,如果找不到key,返回undefined。
>> Map.prototype.has(key) 返回一个布尔值,表示某个键是否在当前 Map 对象之中。
>> Map.prototype.delete(key) 删除某个键,返回true。如果删除失败,返回false。
>> Map.prototype.clear() 清除所有成员,没有返回值。
遍历方法
>> Map.prototype.keys() 返回键名的遍历器。
>> Map.prototype.values() 返回键值的遍历器。
>> Map.prototype.entries() 返回所有成员的遍历器。Map 结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。
Map.prototype[Symbol.iterator] === Map.prototype.entries // true
>> Map.prototype.forEach() 以插入顺序对 Map 对象中的每一个键值对执行一次参数中提供的回调函数。
>>Map 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
>>>.
function logMapElements(value, key, map) {
console.log("m[" + key + "] = " + value);
}
new Map([["foo", 3], ["bar", {}]]).forEach(logMapElements); //↲ m[foo] = 3 //↲ m[bar] = [object Object]
*3. WeakSet 和 WeakMap
>>> WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
>>> WeakMap结构与Map结构类似,也是用于生成键值对的集合。
const ws = new WeakSet();
const wm = new WeakMap();
>>.WeakSet与WeakMap 中的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内
存。也就是说,一旦不再需要,WeakSet与WeakMap 中的数据会自动消失,不用手动删除引用。
》》》》》》模块之:class 类 ===============================================☆=
1.class
概念:ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
>>>.
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toS = function () {return '(' + this.x + ', ' + this.y + ')';};
var p = new Point(1, 2);
等同:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toS() {return '(' + this.x + ', ' + this.y + ')';}
}
>>.上面代码定义了一个"类",可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象,另外,定义"类"的方法的时候,不需要加上
function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
1-1.直接在类中定义的方法,效果实际上就是ES5在构造函数的 prototype对象 添加方法。
示例:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toS() {return '(' + this.x + ', ' + this.y + ')';}
}
var m = new Point();
m.__proto__.toS === Point.prototype.toS // true
1-2.除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。该方式定义的属性区别于在class体内定义的方法(1-1),后者是定义在构造函数的
prototype对象上,而前者则是直接为生成的实例对象添加属性。
示例:
class foo { var m = new foo();
bar = 'hello'; m.bar// hello
constructor() {}
}
2.class内部机制
>1.严格模式:类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
>2.不存在提升:类不存在变量。不允许出现使用在前,定义在后,这样会报错;name 属性:另外,每个class都有一个name属性,该属性值为class关键字后面的类名。
>3.Generator 方法:如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。
>4.this 的指向: 类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
示例:
class Logger {
lll() {
console.log(this); //undefined
this.p();
}
ooo = ()=>{this.p();}
p() {console.log('>>>>>>>');}
}
const logger = new Logger();
const { lll, ooo } = logger;
ooo(); // '>>>>>>>'
lll(); //undefined↲ Cannot read property 'print' of undefined↲
>>.class logger中的方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this 会指向该方法运行时所在的环境(由于class 内
部是严格模式,所以 this 实际指向的是undefined),注意箭头函数的指向。
3.constructor() 方法
>>.通过使用 new 命令由 class 生成对象实例时,实际上就是返回 constructor() 的返回值。一个类必须有constructor方法,如果没有显式定义,一个空的
constructor() 会被默认添加。
>>.constructor方法默认返回实例对象(即this)。该返回值可以修改为其他对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo // false constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。
>>.类必须使用new调用,否则会报错(调用类的静态方法时不需要使用 new 关键字)。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
class Point { ...}
var point = Point(2, 3);// 报错
var point = new Point(2, 3);// 正确
>>.生成类的实例的写法,与 ES5 完全一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用Class,将会报错。
>>.与 ES5 一样,类的所有实例共享一个原型对象。
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__ === p2.__proto__ //true
4.class 的继承
>>.class 可以通过 extends关键字 实现继承,继承可以继承父类的所有属性和方法。extends
>>.与 ES5 的通过修改原型链实现继承的方式原理一样,同样可以继承父类的原型链。
★>>.一个类继承了其他类,子类必须在constructor方法中调用super方法,super关键字 表示父类的构造函数,否则新建实例时会报错。这是因为子类自己的this对象,必
须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性。
>>>.
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
}
>>.ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对
象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
>>.如果子类没有定义 constructor() 方法,这个方法会被默认添加。也就是说,不管有没有显式定义,任何一个子类都有 constructor() 方法。在子类的构造函数中,
只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。最后,父类的静态方法,
也会被子类继承。
4.1.原生构造函数的继承
>>.原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript 的原生构造函数大致有下面这些:
Boolean()、Number()、String()、Array()、Date()、Function()、RegExp()、Error()、Object()
>>.ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。
class MyArray extends Array {}
var arr = new MyArray();
arr[0] = 12;
arr[0] // 12
arr.length = 0;
arr[0] // undefined
>>.上面代码定义了一个MyArray类,继承了Array构造函数,因此就可以从MyArray生成数组的实例。这是 ES5 无法做到的。
5.super关键字
>>.super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
>>.super作为函数时,代表父类的构造函数。只能用在子类的构造函数之中。
>>.super作为对象时,在普通方法中,指向父类的原型对象。在静态方法中,指向父类。
示例1: 示例2:
class A { class A {
p() { x = 1;
return 2; print() { console.log(this.x); }
} }
}
class B extends A {
class B extends A { x = 2;
constructor() { m() { super.print(); }
super(); }
console.log(super.p());
} let b = new B();
} b.m(); // 2
let b = new B();// 2
>>.示例1中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。
在 A 内部添加的方法,实际上就是在A.prototype对象 上添加方法。
>>.示例2中,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
6.取值和存值
>>.在"类"的内部可以使用get和set关键字,为指定属性设置"存值函数"和"取值函数",拦截该属性的存取行为。
>>>.
class MyClass {
constructor() {....}
get prop() {return 'getter';}//指定属性"prop"
set prop(value) {console.log('setter: '+value);}//指定属性"prop"
}
let inst = new MyClass();
inst.prop = 123;// setter: 123
inst.prop;// 'getter'
>>.上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。
7.静态方法
>>.在类中定义的方法,都会被实例继承。若在方法前,加上static关键字,则该方法不会被实例继承,而是直接通过类来调用,这就称为"静态方法"。
>>>.
class Foo {
static classMethod() {return 'hello';}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod(); // TypeError: foo.classMethod is not a function
>>.静态方法直接在类上调用"Foo.classMethod()",而不是在类的实例上调用。如果静态方法包含 this 关键字,这个 this 指的是类,而不是实例。
>>.静态方法可以与非静态方法重名。父类的静态方法,可以被子类继承,继承之后即可使用子类直接调用,静态方法也是可以从 super 对象上调用的。
8.new.target属性
>>. new 关键字是用来使构造函数生成实例对象的命令。ES6为该关键字引入了一个 new.target 属性,该属性一般用在构造函数之中,返回 new 命令作用于的那个构造函数
>.如果构造函数不是通过 new 命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
>.class 内部调用new.target,返回当前 class,子类继承父类时,new.target会返回子类。利用这个特点,可以写出不能独立使用、必须继承后才能使用的类:
>>>.
class Shape {
constructor() {
if (new.target === Shape)throw new Error('本类不能实例化')
}
}
class Rectangle extends Shape { ... }
var y = new Rectangle(3, 4); // 正确
var x = new Shape(); // Uncaught Error: 本类不能实例化
★9.class的本质以及继承链(建议复习ES5原型链章节,深刻理解之后再阅读以下内容)
>>.每个对象都有__proto__属性,指向其构造函数的 prototype 属性。class 构造实例对象,所以其本质依旧是函数,函数同时拥有 prototype 属性和 __proto__ 属性。
class的原型链示例:
class A {}
var a = new A();
/* ES6的class(非继承)的prototype属性是一个对象,该对象拥有两个属性:constructor 和 __proto__,同ES5是一致的 */
A.prototype
/*输出: {constructor: ƒ}
↲constructor: class A >>.constructor指向 本身
↲__proto__: Object(类型) >>. __proto__指向 Object.prototype,说明该对象由Object构造函数构造 */
/* class(继承自父类)的__proto__属性指向其构造函数的prototype属性 */
A.__proto__ === Function.prototype //true
a.__proto__ === A.prototype //true
A.prototype.__proto__ === Object.prototype //true
>>.以上代码可以看出,class就是普通构造函数的语法糖,原型链与普通函数的原型链没有任何差异。
>>.关键字"extends"带来的继承
>>.class可以使用关键字"extends",当时用该关键字继承的子类构造函数和父类构造函数存在着原型链上的链接,该机制是ES6之后class的继承独有的。
示例:
class ASub extends A {}
var asub = new ASub();
asub.__proto__ === ASub.prototype // true 符合原型链
/* 此时,子类的"__proto__"属性直接指向 A */
ASub.__proto__ === A
/* 注意该处的区别,以往的顶层构造函数的"__proto__"属性都是指向 Object.prototype 对象,而该处指向其父类的 prototype 对象 */
ASub.prototype.__proto__ === A.prototype // true
>>.类的继承是按照下面的模式实现的:
class B {}
class BSub {}
// BSub 的实例继承 B 的实例
Object.setPrototypeOf(BSub.prototype, B.prototype);
// BSub 继承 B 的静态属性
Object.setPrototypeOf(BSub, B);
const bsub = new BSub();
BSub.__proto__ === B // true
BSub.prototype.__proto__ === B.prototype //true
>>.Object.setPrototypeOf(obj, prototype) 用来设置一个对象的原型对象("__proto__"属性),它是 ES6 正式推荐的设置原型对象的方法。
》》》》》》模块之:异步之Promise ========================================☆
-
=Promise构造函数
>>.Promise.prototype.proto === Object.prototype
>>.Promise.proto === Function.prototype
>>.ES6为任何类型的异步操作提供统一的API即 Promise() 构造函数。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步
操作)的结果。
>>.Promise构造的实例对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),状态不受外界影响。一旦状态改
变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled或者变为rejected。
>>.Promise实例对象一旦被创建,就会立即执行,无法中途取消。如果不设置回调函数,对象内部抛出的错误,不会反应到外部。另外,当处于pending状态时,无法
得知目前进展到哪一个阶段
2.实现以及内部原理
示例:实例化Promise对象
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});//promise()构造函数的内部参数是一个函数体,下文简称:executor 函数
>>.executor函数会在Promise()构造函数返回所创建 promise实例对象前被调用,同时该函数带有 resolve 和 reject 两个参数,这两个参数依旧是两个方法体参数,
由 JavaScript 引擎提供自动部署。
>>.executor 内部通常会执行一些异步操作,一旦异步操作执行完毕,则会调用参数函数,调用情景如下:
>>.若调用resolve函数,则代表结果为正常情况,此时将promise状态改成fulfilled,并将异步操作的结果,作为参数传递出去。
>>.若调用reject 函数,则代表结果为异常情况,此时将promise的状态改为rejected,并将异步操作报出的错误,作为参数传递出去。当executor函数中抛出一个
错误,那么该promise 状态为rejected。executor函数的返回值被忽略。
示例:
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return; getJSON("/posts.json").then(
} function(json) {
if (this.status === 200) { console.log('Contents: ' + json);
resolve(this.response); }, function(error) {
} else { console.error('出错了', error);
reject(new Error(this.statusText)); }
} );
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
★3.resolve()/reject()函数
>>.如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。可以用then()方法分别指定resolved状态和rejected状态的回调函数。reject函
数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可以是另一个 Promise 实例。
示例:
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result))
.catch(error => console.log(error))
>>.上述代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己
的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。
3-1.运行机制
>>.resolve或reject并不会终结 Promise 的参数函数的执行
示例:
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// ↲2 ↲1 (先输出2,再输出1)
>>.resolve()/reject()函数 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
>>.调用resolve()/reject()函数以后,Promise 的使命就完成了,后继操作应该放到then()方法里面,而不应该写在resolve()/reject()函数的后面。
★4.then()方法–回调函数
>>. Promise.prototype.then(onFulfilled(), onRejected()) 方法
>>. then()方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。then()方法的第一个参数是resolved状态的回调
函数,第二个参数(可选)是rejected状态的回调函数。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。
4-1.入参处理:
>>.当Promise构造的实例对象变成拒绝状态(rejection)时,onRejected()参数函数 将作为回调函数被调用。该函数有一个参数,即拒绝的原因。
>>.当Promise构造的实例对象变成接受状态(fulfilled)时,onFulfilled()参数函数 将作为回调函数被调用。
>>.onFulfilled()接受一个参数,即接受的最终结果。如果传入的参数类型不是函数,则会在内部被替换为(x) => x ,即原样返回最终结果的函数:
var p = new Promise((resolve, reject) => {
resolve('foo')
})
/* 'bar'不是函数,所以返回值"foo"字符串 会在内部被替换为 (x) => x 的形式,此时,then返回的Promise将会成为接受状态,并且将返回
的值作为接受状态的回调函数的参数值。返回值下面讨论。 */
p.then('bar').then((value) => {
console.log(value) // 'foo'
})
/* 此时,then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。返回值下面讨论。 */
p.then(()=> ("ssssss")).then((value) => {
console.log(value) // 'sssssss'
})
4-2.返回值处理:
>>.再次提醒:then方法返回的是一个新的Promise实例对象(注意,不是原来那个Promise实例)
>>.情况分为下列几种:
=== 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
=== 如果then中的回调函数没有返回值,那么then返回的Promise将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined。
=== 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
=== 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作
为该被返回的Promise的接受状态回调函数的参数值。
=== 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作
为该被返回的Promise的拒绝状态回调函数的参数值。
=== 如果then中的回调函数返回一个未定状态(pending)的 Promise,那么then返回 Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,
它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。
示例:
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),
err => console.log("rejected: ", err)
);
>>.上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise 对象状态
发生变化。
5.catch()方法–异常处理
>>. Promise.prototype.catch() 该方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
示例:
getJson('/posts.json')
.then(function(posts) {
// ...
})
.catch(function(error) {
// 处理 getJson 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
>>.上述代码中,getJson方法返回一个 Promise 实例对象,如果该对象状态为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变
为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
>>.Promise 实例对象的错误具有"冒泡"性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
示例:
getJson('/post/1.json')
.then(function(post) {
return getJson(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
>>.一共有三个 Promise 对象:一个由getJson产生,两个由then产生。它们之中任何一个抛出的错误,都会被最后一个catch捕获。
>>.一般来说,不要在then方法里面定义 reject 状态的回调函数(即then的第二个参数),总是使用catch方法。catch方法可以捕获前面then方法执行中的错误。
>>.如果没有使用 catch 方法或者 rejection() 方法指定错误处理的回调函数,Promise 对象抛出的错误就不会传递到外层代码。Promise 内部的错误不会影响到
Promise 外部的代码。
*6.finally()
>>. Promise.prototype.finally()
>>.该方法是 ES2018 引入标准的。不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
7.其他方法
7-1.Promise.resolve() Promise.resolve()可以将对象转为 Promise 对象
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
// 将 jQuery 生成的deferred对象,转为一个新的 Promise 对象。
>>.该方法接受一个参数,根据不同参数类型,具体的处理方式分成四种:
=== 如果参数类型是一个 Promise 实例对象,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
=== 如果参数类型是一个 thenable 对象,Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。
示例:
let thenable = {then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
>>.上面代码中,thenable对象的then()方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then()方法指定的回调函数,输出 42。
=== 如果参数类型是一个不具有then方法的对象,或根本就不是对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
=== 如果不带任何参数。直接返回一个resolved状态的 Promise 对象。
7-1-1.thenable对象
>>.thenable对象指的是具有then方法的对象
>>. JQuery.ajax() 返回值就是,即是jqXHR Object
7-2.Promise.all()
const p = Promise.all([p1, p2, p3]);
>>.Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
>>.该方法接受一个数组参数。成员必须是 Promise 实例,或者能够返回Promise 实例的对象或集合,如果不是 Promise 实例,就会先调用Promise.resolve方法。
>>.p的状态由p1、p2、p3决定,分成两种情况:
>1.只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
>2.只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
7-3.Promise.race()
const p = Promise.race([p1, p2, p3]);
>>.Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
>>.该方法接受一个数组参数。成员必须是 Promise 实例,或者能够返回Promise 实例的对象或集合,如果不是 Promise 实例,就会先调用Promise.resolve方法。
>>.如果参数中的Promise 实例,有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
7-4.Promise.reject(reason) 返回一个新的 Promise 实例,该实例的状态为rejected。
》》》》》》模块之:异步之Generator =======================================================☆=
1.Generator
>>.该关键字属于一个构造函数,但是无法显示,根绝MDN文档,Generator 继承于 Function 构造函数。
>>.生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议。
1-1.函数理解
>>.语法上,可以认为,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,因此 Generator 函数是一个遍历器
(Iterator)对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
>>.形式上,Generator 函数是一个普通函数,但是有两个特征:
>1.function关键字与函数名之间有一个星号" * ";
>2.函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是"产出")。
1-2.函数使用
>>.Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。调用 Generator 函数后,该函数并不执行,并且返回一个指向内部状态的指针对象,
也就是一个遍历器对象。该遍历器对象拥有一个 next() 方法。
===.核心:Generator 函数是分段执行的,yield 表达式是暂停执行的标记,而 next() 方法可以恢复执行
>>.调用 next() 方法可以操作 Generator 函数的遍历器对象,每次调用,将从函数头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式(或return语
句)为止。
function* myGenerator() {
yield 'world';
return 'ending';
}
var hw = myGenerator();//注意:不要写成对象实例化形式:var hw = new myGenerator(); //报错
hw.next();// { value: 'world', done: false }
hw.next();// { value: 'ending', done: true }
hw.next();// { value: undefined, done: true }
>>.done属性是一个布尔值,表示是否遍历结束。
>>.ES6 没有规定function关键字与函数名之间的星号" * "的位置,写在"function"与方法名之间的任何位置都可以,建议:function* foo(x, y) { ··· }
1-3.运行原理 -- yield表达式
1.3.1.>>.由于 Generator 函数返回的遍历器对象,只有调用遍历器对象的 next() 才会遍历下一个内部状态,因此,提供了一种可以暂停执行的函数。yield表达式 就
是暂停标志。
遍历器对象的 next() 方法的运行逻辑如下:
>>>1.遇到yield表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的value属性值。
>2.下一次调用 next() 方法时,再继续往下执行,直到遇到下一个 yield 表达式。
>3.如果没有再遇到新的 yield表达式,就一直运行到函数结束,直到 return语句 为止,并将 return语句 后面的表达式的值,作为返回的对象的 value
属性值。
>4.如果该函数没有 return语句,则返回的对象的 value属性值 为 undefined。
>>.需要注意的是:yield 后面的表达式,只有当调用next()方法,内部指针指向该语句时才会执行。
>>.yield表达式与return语句异同:
>.相似之处在于,都能返回紧跟在语句后面的那个表达式的值
>.区别之处在于,每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能
1.3.2.>>.yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。另外,yield表达式如果用在另一个表达式之中,必须放在圆括号里面。
>>>.
function* demo() {
console.log('Hello' + (yield 123)); // OK,console.log() 在输出前肯定要先计算后面括号中的参数值,所以"yield"比输出语句先执行
//console.log('Hello' + yield 123); // SyntaxError
}
var m = demo();
m.next();//{value: 123, done: false}
m.next();//Helloundefined ↲{value: undefined, done: true}
>>>.
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
a.forEach(function (item) {
yield item;//SyntaxError forEach方法的参数是一个普通函数,里面使用了yield表达式
});
};
★2.与 Iterator 接口的关系
>>.所有对象的 Symbol.iterator 方法,等于该对象的遍历器生成函数。因此可以把 Generator 函数赋值给对象的 Symbol.iterator 属性,从而使得该对象具有 Iterator
接口。
>>>.
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield “ss”;
yield 33;
yield “你好”;
};
[…myIterable] // [“ss”, 33, “你好”]
>>.上面代码中,Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了 Iterator 接口,可以被...运算符遍历了。
3.遍历器对象的 next()方法
>>.可设置参数,该参数表示上一个 【yield表达式】 的返回值,该值用于替代当前语句块的上一个"yield"的表达式的值。
>>>.
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
>>. 第一次调用b的next方法时,返回x+1的值6;
第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;
第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。
>>.由于 next() 方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。
5.for…of循环
>>.for...of循环可以自动遍历 Generator函数 运行时生成的 Iterator对象,且此时不再需要调用next方法。
>>>.
function* foo() {
yield 1;
yield 2;
return 3;
}
for (let v of foo()) {
console.log(v); //1↲ 2↲
}
>>.一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的3,不包括在for...of循环之中。
>>.除了for...of循环外,解构赋值、"..."运算符和Array.from() 等方法,在内部实现调用的正是遍历器接口。
6.其他方法
6-1.Generator.prototype.throw()方法
>>.Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内。
>>.throw方法 可以接受一个参数,该参数会被 Generator 函数体内的 catch语句 接收,建议抛出 Error对象 的实例。
>>>.
var g = function () {
try {
yield;
} catch (e) {
console.log(e);
}
};
var i = g();
i.next();
i.throw(new Error('出错了!'));// Error: 出错了!(…)
>>.不要混淆遍历器对象的throw方法和全局的throw命令。全局的throw命令抛出的错误只能被函数体外的catch语句捕获。
>>.若Generator 函数体内没有catch语句,且遍历器对象的throw方法抛出错误,此时,若外部有catch语句块,则会被外部catch语句块捕获。否则系统将直接报错。
>>.throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。因为第一次执行next方法,等同于启动执行 Generator 函数的内部代码。throw方法被捕
获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法,不影响下一次遍历。
>>.Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。若Generator 执行过程中抛出错
误,且没有被内部捕获,则JavaScript引擎认为这个 Generator 已经运行结束了。之后调用next方法,将返回{value:undefined,done:true}的对象。
6-2.Generator.prototype.return()方法
>>.Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
>>.如果 Generator 函数内部有try...finally代码块,且正在执行try代码块,那么return方法会推迟到finally代码块执行完再执行。
6-3.next()、throw()、return()的共同点
理解:它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换 yield 表达式。
>1.next() 是将yield表达式替换成一个值。
>2.throw() 是将yield表达式替换成一个throw语句。
>3.return() 是将yield表达式替换成一个return语句。
7.Generator 函数的this
>>.Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。
>>>.
function g() {
this.a = 11;
}
g.prototype.hello = function () {
return ‘hi!’;
};
let obj = g();
obj instanceof g // true
obj.hello() // ‘hi!’
obj.a // undefined
>>.函数g返回的遍历器obj,是g的实例,而且继承了g.prototype。Generator 函数g在this对象上面添加了一个属性a,但是obj对象拿不到这个属性。Generator 函数也不
能跟new命令一起用,会报错。因为g返回的总是遍历器对象,而不是this对象。
8.其他使用
8.1.yield 表达式
>>.yield* 表达式,就是用来在一个 Generator 函数里面执行另一个 Generator 函数。实际上,任何数据结构只要有 Iterator 接口,就可以被yield*遍历。
8.2.作为对象属性的 Generator 函数
>>.如果一个对象的属性是 Generator 函数,可以简写成下面的形式。
let obj = {
* myGeneratorMethod(){···}
};
>>.上面写法等价于下面写法,myGeneratorMethod属性前面有一个星号,表示这个属性是一个 Generator 函数。
let obj = {
myGeneratorMethod: function* () {···}
};
》》》》》》模块之:异步之async ===================================☆=
1.理解
*1.1.AsyncFunction 构造函数
>>.AsyncFunction 构造函数用来创建新的 异步函数 对象,JavaScript 中每个异步函数都是 AsyncFunction 的对象。
>>.AsyncFunction 并不是一个全局对象,需要通过下面的方法来获取:
Object.getPrototypeOf(async function(){}).constructor
1.2.async function 函数
>>.async function 用来定义一个返回 AsyncFunction 对象的异步函数。异步函数是指 通过事件 循环 异步执行 的函数,它会通过一个隐式的 Promise 返回其结果。
我们称之为 "async函数"。
>>.异步函数可以包含 await 指令,该指令会暂停异步函数的执行,并等待 Promise 执行,然后继续执行异步函数,并返回结果。await 关键字只在异步函数内有效。如
果你在异步函数外使用它,会抛出语法错误(这段话中的"异步函数"可以代指"async函数",下同)。
>>.注意,当异步函数暂停时,它调用的函数会继续执行(收到异步函数返回的隐式Promise)
>>.async/await 的目的是简化使用多个 Promise 时的同步行为。正如 Promise 类似于结构化回调,而 async/await 则更像是Generator 和 Promise 的结合。
Generator函数:
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
};
写成async函数:
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
};
var m = asyncReadFile();// 执行
m.__proto__ === Promise.prototype //true async函数返回的是一个 Promise 对象
2.async函数
>>.函数声明 async function 函数名() { ... }
>>.class使用
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then( … );
★2.1.async函数返回值
>>.async函数返回的是一个 Promise 对象,可以使用 then()方法 添加回调函数。async函数内部return语句返回的值,会成为返回值的 Promise 对象的then方法回调
函数的参数。
>>.返回的 Promise 对象必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。只有async函数内部的异步
操作执行完,才会执行then方法指定的回调函数。
>>.async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
★2.2.await关键字
>>.await 操作符用于等待一个Promise 对象。它只能在异步函数 async function 中使用。await 紧跟着的表达式表示:一个 Promise 对象或者任何要等待的值。
>>.当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
2.2.1.await的返回
★>>.如果await紧跟着的表达式返回一个Promise对象实例,则 await 将等待 Promise 正常处理完成并返回其处理结果。如果该值不是一个 Promise,await 会把
该值转换为已正常处理的Promise,然后等待其处理结果返回。
2.3.注意点
>>.多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。.await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放
在try...catch代码块中。
2.异常处理
>>.在async函数中,任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
但有时我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将await放在try...catch结构里面,这样不管这个异步操作是否成功,后面的await都会
执行。
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
或者,可以这样:await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
★3.async 内部运行机制
3-1.JavaScript运行机制
JavaScript是单线程语言,所有的异步都是利用运行顺序模拟安排出来的结果
>>.线程 所有的语句都是在该线程上运行,只是先后运行的关系
>1.宏任务(macro-task) 每个宏任务会被放入队列池(Event Queue),之后,线程若空出来则按放入的先后顺序执行
>1.1. 运行Script代码时,整体作为第一个宏任务,并开始启动运行
>1.2. 包括整体代码script,setTimeout,setInterval
>1.3. 如果当前的宏任务下有微任务,则执行完所有的微任务后才能空出线程,执行排在后面的宏任务
>2.微任务(micro-task() 每个微任务会被放入队列池(Event Queue),之后,线程若空出来则按放入的先后顺序执行
>2.1. Promise,process.nextTick
3-2.async 内部运行机制
首先,开始运行的Script作为第一个宏任务进入线程开始运行
>>>.在 async 外部:
当遇到 new Promise 时,会即刻运行该实例的executor函数,并将该实例的回调函数(then函数)放入微任务队列
>>>.在 async 内部:
当遇到 await 时,返回一个 new Promise 并将该实例的回调函数(then函数)放入微任务队列,并且跳出且跳过 async 函数,往下执行(也就是执行当前宏任务
剩余的语句)
若,当前宏任务语句执行结束,则按放入顺序开始执行微任务队列中的任务,直到遇到由 async 函数返回的 new Promise 实例生成的微任务时,执行完该微任
务,然后,跳回到 async 函数内部生成该微任务语句的下一条语句开始执行,如此,周而复始,直至完成所有的语句
若 await 后面本身就是一个 Promise 实例,则会发生:先将 Promise 实例放入线程池,然后再将本身生成的 Promise 实例放入线程池
>>>.async函数运行机制示例 >>>> 查询库 >> 5.
>>>.示例
function hh() {
console.log("hhh"); 输出:
return "HHHHHHHHHHHH"; qqq
} 111111111111
async function kk() { hhh
console.log("kkk"); ppp
return Promise.resolve("666666666666"); 555555555555
} qqqqqqqqqqqq
var qq = new Promise((resolve)=> { 222222222222
console.log("qqq"); kkk
resolve("qqqqqqqqqqqq"); pppppppppppp
}); HHHHHHHHHHHH
qq.then((val)=> console.log(val)); 666666666666
async function test() {
console.log("111111111111"); ★>>.任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都
const v1 = await hh(); 会中断执行。
console.log('222222222222');
// await Promise.reject('出错了'); // 该语句会导致整个async函数都会中断执行,对函数体外的语句不影响
const v2 = await kk();
console.log(v1);
console.log(v2);
}
test();
var pp = new Promise((resolve)=> {
console.log("ppp");
resolve("pppppppppppp");
});
pp.then((val)=> console.log(val));
console.log("555555555555");
*4.同步和异步
>>.多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
>>.上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时
触发。
>>. let [foo, bar] = await Promise.all([getFoo(), getBar()]);
》》》》》》模块之:Module 语法 ===========================================================☆=
1.Module 的语法概念介绍
>>.ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
import { stat, exists, readFile } from 'fs';
>>.上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为"编译时加载"或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模
块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
*>>.由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静
态+分析实现的功能。
>>.ES6 模块还有以下好处:
>1.不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
>2.将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
>3.不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
>>.ES6 模块特点:
>.ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict;"。
>.ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
2.export命令
>>. export 命令用于规定模块的对外接口,一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使
用export关键字输出该变量。
>>.两种写法:
1:输出变量 2:使用大括号指定所要输出的一组变量
export var lastName = 'Jackson'; var lastName = 'Jackson';
export var year = 1958; var year = 1958;
export { lastName, year };
2.1. export 命令除了输出变量,还可以输出函数或类(class)
>>>.
export function multiply(x, y) {
return x * y;
};
2.2. export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
>>>.
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
>>.上面代码输出变量foo,值为bar,500 毫秒之后变成baz。
2.3.可以使用as关键字重命名
>>>.
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
>>.上面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。
2.4.特别注意:export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错。
3.import命令
>>.使用 export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
示例:
import {firstName, lastName, year} from './profile.js';
>>.上面代码的import命令,用于加载profile.js文件,并从中输入变量。import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,
必须与被导入模块(profile.js)对外接口的名称相同。
3.1.import命令输入的变量都是只读的
>1.也就是说,不允许在加载模块的脚本里面,改写接口。
>2.但是,如果是一个对象,改写对象属性是允许的。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,轻易不要改变它的属性。
3.2.整体加载
>>.除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
逐一加载:
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
整体加载:
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
3.3.可以使用as关键字重命名
import { lastName as surname } from './profile.js';
3.4.import 使用注意点
>.import命令具有提升效果,会提升到整个模块的头部,首先执行。
>.import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
>.通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在一起,最好不要这样做。因为import在静态解析阶段执行,所以它是一个
模块之中最早执行的
4.export default命令
>>.使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出
function foo() {
console.log('foo');
}
export default foo;// 默认输出一个函数,并且不加大括号。
--
import customName from './export-default';
customName(); // 'foo'
>>.其他模块加载该模块时的默认输出时,import命令可以为该匿名函数指定任意名字。且此时import命令后面,不使用大括号。
>>.export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。并且不加大括号,因为只可能唯一对
应export default命令。
>>.export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。
function add(x, y) {
return x * y;
}
export {add as default};// 等同于 export default add;
--
import { default as foo } from 'modules';// 等同于 import foo from 'modules';
>>.正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。
var a = 1;
export default a;// 正确
export var a = 1;// 正确
>>.因为export default命令的本质是将后面的值,赋给default变量,所以可以直接将一个值写在export default之后。
export default 42;// 正确
export default var a = 1;// 错误
>>.可以一条import语句中,同时输入默认方法和其他接口
import xxxx, { each, forEach } from 'xxxxx';
5.export 与 import 的复合写法
>>.如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
>>>.
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
// 接口改名
export { foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';
// 默认接口
export { default } from 'foo';
*6.模块的继承:export * 命令,表示再输出模块的所有属性和方法,注意:export * 命令会忽略模块的default方法。
*7.import() -- 最新提案,了解即可
>>.import 命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行。也就是说,import 和 export 命令只能在模块的顶层,不能在代码块之中(如在if代码
块,或在函数之中)。因此,有一个提案,建议引入import()函数,完成动态加载。
》》》》》》模块之:Proxy代理对象 ☆================================
-
Proxy 对象
>>> var pro = new Proxy(target, handler);
--target >> 将被代理的对象参数
--handler >> 一个对象,用来定制拦截行为
.要使得Proxy起作用,须对Proxy实例(即pro)进行操作,而不是对目标对象。操作的Proxy实例发生的变化将会映射到原对象上面,没有设置拦截的操作,则直接映射在目
标对象上,按照原先的方式产生结果。
.
var m ={};
var proxy = new Proxy(m, {
get: function(target, property) { return 35; }
});
proxy.GG = 100;
console.log(m.GG + proxy.GG)//100+35 = 135
.Proxy 用于修改某些操作的默认行为,属于一种"元编程"(meta programming),即对编程语言进行编程。可以理解成,在目标对象之前架设一层"拦截",外界对该对
象的访问,都必须先通过这层拦截,因此可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来"代理"某些操作,可以译为"代理器"。
2.Proxy支持的拦截操作一览,一共13种 >>>> 查询库 >> 3.
3.回收代理
>>> Proxy.revocable(target, handler) 创建一个可撤销的代理对象
--target >> 用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
--handler >> 一个对象,其属性是当执行一个操作时定义代理的行为的函数
返回值 >> 返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke},
proxy >> 表示新生成的代理对象本身,和用一般方式 new Proxy() 创建的代理对象没什么不同,只是它可以被撤销掉
revoke >> 撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象
>>>.
var revocable = Proxy.revocable({}, {
get(target, name) {return name;}
});
var proxy = revocable.proxy;
proxy.foo; // "foo"
revocable.revoke(); // 执行撤销方法
proxy.foo; // TypeError
proxy.foo = 1 // 同样 TypeError
delete proxy.foo; // 还是 TypeError
typeof proxy // "object",因为 typeof 不属于可代理操作
4.Proxy的this问题 >> 虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就
是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。
》》》》》》模块之:Reflect对象 =☆===========================================
-
Reflect 对象
.一个内置的对象,Reflect不是一个构造函数,他是一个对象。它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。Reflect与Proxy一样,是 ES6
为了操作对象而提供的新 API。
.设计该对象目的:
*1.将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来
的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
示例:(该示例只是用于体现Object该构造函数直接调用的使用情景,而 Reflect 对象则是用来替换类似场景的一个新对象)
var obj = {};
var descriptor = Object.create(null); // 没有继承的属性,默认没有 enumerable,没有 configurable,没有 writable
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);
Object.defineProperty(obj, "key", {
enumerable: false,
configurable: false,
writable: false,
value: "static"
});
*2.修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而
Reflect.defineProperty(obj, name, desc)则会返回false。
*3.让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和
Reflect.deleteProperty(obj,name)让它们变成了函数行为。
4.Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方
法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
>>>.
var loggedObj = new Proxy(obj, {
get(target, name) {
console.log('get', target, name);
return Reflect.get(target, name);
}
});
>>.每一个Proxy对象的拦截操作(get等),内部都调用对应的Reflect方法,保证原生行为能够正常执行,同时额外输出一行日志。
2.Reflect的静态方法一览,一共13种 >>>> 查询库 >> 4.
=☆=☆☆====== Ajax ==================
》》》》》》基础模块之:使用 =☆===
》》》》》》基础模块之:深入 ☆============================================================================
》》》》》》基础模块之:Level 2 ☆============
xhr.timeout = 3000;
>>.上面的语句,将最长等待时间设为3000毫秒。过了这个时限,就自动停止HTTP请求。与之配套的还有一个timeout事件,用来指定回调函数。
xhr.ontimeout = function(event){
alert('请求超时!');
}
>>.目前,Opera、Firefox和IE 10支持该属性,IE 8和 IE 9的这个属性属于XDomainRequest对象,而Chrome和Safari还不支持。
3.FormData对象
>>.ajax操作往往用来传递表单数据。为了方便表单处理,HTML 5新增了一个FormData对象,可以模拟表单。
首先,新建一个FormData对象
var formData = new FormData(); //FormData.__proto__ === Function.prototype
然后,为它添加表单项
formData.append('username', '张三');
formData.append('id', 123456);
最后,直接传送这个FormData对象。这与提交网页表单的效果,完全一样
xhr.send(formData);
FormData对象也可以用来获取网页表单的值
/*注意使用原生Js获取form元素 */
var form = document.getElementById('myform');
var formData = new FormData(form);
formData.append('secret', '123456'); // 添加一个表单项
xhr.open('POST',form.action);
xhr.send(formData);
4.上传文件
XHR2对象,不仅可以发送文本信息,还可以上传文件。
假定files是一个"选择文件"的表单元素(input[type="file"]),我们将它装入FormData对象。
var formData = new FormData();
for (var i = 0; i < files.length;i++) {
formData.append('files[]', files[i]);
}
然后,发送这个FormData对象。
xhr.send(formData);
5.跨域资源共享(CORS)
>>.XHR2对象,可向不同域名的服务器发出HTTP请求。这叫做"跨域资源共享"(Cross-origin resource sharing,简称CORS)。使用CORS的前提是,浏览器必须支
持这个功能,而且服务器端必须同意这种"跨域"。如果能够满足上面的条件,则代码的写法与不跨域的请求完全一样。目前,主流浏览器都支持CORS,除 IE8和 IE9。
6.接收二进制数据
>>.XHR2对象新增的responseType属性,可以从服务器取回二进制数据。如果服务器返回文本数据,这个属性的值是"TEXT",这是默认值。较新的浏览器还支持其他
值,也就是说,可以接收其他格式的数据。
你可以把responseType设为blob,表示服务器传回的是二进制对象。
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png');
xhr.responseType = 'blob';
接收数据的时候,用浏览器自带的Blob对象即可。
var blob = new Blob([xhr.response], {type: 'image/png'});
注意,是读取xhr.response,而不是xhr.responseText。
你还可以将responseType设为arraybuffer,把二进制数据装在一个数组里。
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png');
xhr.responseType = "arraybuffer";
接收数据的时候,需要遍历这个数组。
var arrayBuffer = xhr.response;
if (arrayBuffer) {
var byteArray = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteArray.byteLength; i++) {
// do something
}
}
7.进度信息
>>.XHR2对象新增 progress事件,用来在传送数据的时候,为我们返 回数据传输进度 的信息。它分成上传和下载两种情况。下载的 progress事件是属于
XMLHttpRequest对象,上传的progress事件属于XMLHttpRequest.upload对象。
我们先定义progress事件的回调函数。
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;
function updateProgress(event) {
if (event.lengthComputable) {
var percentComplete = event.loaded / event.total;
}
}
上述代码中,event.total是需要传输数据的总字节,event.loaded是已经传输数据的字节。如果event.lengthComputable不为真,则
event.total等于0。与progress事件相关的,还有其他五个事件,可以分别指定回调函数:
* load事件:传输成功完成。
* abort事件:传输被用户取消。
* error事件:传输中出现错误。
* loadstart事件:传输开始。
* loadEnd事件:传输结束,但是不知道成功还是失败。
8.附上XHR2的主流浏览器兼容性
1.>IE8/IE9、Opera Mini 完全不支持xhr对象
2.>IE10/IE11部分支持,不支持 xhr.responseType为json
3.>部分浏览器不支持设置请求超时,即无法使用xhr.timeout
4.>部分浏览器不支持xhr.responseType为blob
=☆=☆☆====== 扩 展 ==================
☆ ☆ ☆模块之:闭包-(倒计时) ☆============
.闭包的使用
>>>.实现多个独立倒计时(同样的倒计时间隔),可动态添加倒计时成员
var niTime = function(){
/*内部变量数组*/
var innerArray = [];
/*倒计时,循环执行内部函数*/
setInterval(innerFun,1000);
/*该方法为函数入口。外部利用该入口填充数据,至该函数体内变量:数组 ---【闭包用法】填充数据单位为对象,每次填充一个对象单位,该单位包括两个属
性:触发倒计时的位置、倒计时的总时间*/
return function(addressid,T){
innerArray.push({address:document.getElementById(addressid),time:T});
}
/*内部函数,循环数组所有的对象,将每个对象的两个属性,一一对应填充*/
function innerFun(){
for(var i =0;i
☆ ☆ ☆模块之:文件处理对象 ============☆
==图片上传示例
Html:
/*multiple 属性规定输入字段可选择多个值。*/