天天看點

ES6快速浏覽ES6快速浏覽

ES6快速浏覽

let和const關鍵字

let

  • 局部變量
  • 作用于塊級作用域

    {}

    ,而

    var

    是函數級作用域
  • 必須先定義後才可使用,而

    var

    定義的變量會先提升成

    undefined

  • 不可重複聲明變量,

    var

const

  • 常量,不可修改
  • 塊級作用域
  • 對象常量,隻不可修改對象整體,對象内部變量可修改,是以需要當機對象或深度當機

全局變量

  • var a

    window.a

  • let

    const

    不可用

    window

變量的解構

數組的解構指派

let [a,b,c] = [4,5,6];//定義時
[a, b] = [20, 30];//後期
let [age, [salary, [name]]] = [20,[3000,["Bill"]]];//嵌套
let [m, n] = ['a'];//a,undefined
let [v1, v2] = [1,2,3,4,5];//1,2
let [value1, value2, ...others] = [0,1,2,3,4,5,6,7,8,9];//others-->2,3,4,5,6,7,8,9
let [t1] = 20;//報錯
let [x1, x2 = 20] = [100];//100,20  預設值
var [f1, f2 = f] = [1];//1,函數f 預設值也可以為函數
           

Generator函數的解構指派

// 隻要某種資料結構具有Iterator接口,都可以采用資料形式的解構指派
// 每執行一次,都會指向yield語句
// Generator函數,天生具有Iterator接口
// Generator每調用一次,會暫停
function* gen(x)
{
       while(true)
       {
              yield x;//執行一次,傳回此時x
              x = x + 2;
       }
}
let [g1, g2, g3, g4] = gen(10);
console.log("g1 = " + g1);//10
console.log("g2 = " + g2);//12
console.log("g3 = " + g3);//14
console.log("g4 = " + g4);//16
           

對象的解構指派

var {name, age} = {age:30, name:"Bill"};
({x,y} = {x:20, y:30});//後期指派
var {value1, value2} = {value1:30};//30,undefined
var {product:a, price:b} = {product:"iPhone7", price:5000};//被指派變量為a;product隻做比對
var {product:a,price} = {product:"iPhone7 Plus", price:6000};//被指派變量為a,price
var obj = {p:["hello",{x:100}]};var {p:[s,{x:y}]} = obj;//嵌套指派
var {x,y=10} = {x:2};//預設值
var {car} = {name:"Bill"};//解構失敗,undefined
var obj = {fun1:function(x){return x*x;},fun2:function(x,y){return x+y;}};let {fun1,fun2} = obj;//可指派函數
           

字元串的解構指派

let [a,b,c,d,e] = 'hello';
           

函數參數的解構指派

function sub([x,y])
{
    return x - y;
}

[[1,2],[4,6]].map(([a,b])=>{let result = a + b; console.log(result);});
           

應用

變量交換

var m = 20;
var n = 30;
[m,n] = [n,m];
           

函數傳回一個數組,将傳回結果賦給多個變量

function multiNames()
{
   return ["Bill", "Mike", "John"];
}
var [name1, name2, name3] = multiNames();
           

參數使用對象形式,進而可以無序傳參

function sub({x,y,z})
{
   return x - y - z;
}
sub({z:20, y:-15,x:10});
           

提取json資料

var jsonData =
{
   id:18,
   name:"Mike",
   salary:4000
};
let {id, name, salary} = jsonData;
           

函數參數使用預設值

function fun(name, {flag = true, num = 100})
{
   console.log(name, flag, num);
}
fun("John",{});  // {}不能省略
           

周遊map結構

任何部署了Interator接口的,都可以用for of周遊

var map = new Map();
map.set('id', 49);
map.set('name', "John");
map.set('age', 20);
for(let [key, value] of map)
{
   console.log(key, value);
}
//  隻擷取key,無法按下面這種方式隻擷取value
for(let [key] of map)
{
    console.log(key);
}
           

字元串的擴充

字元串變編碼

  • .charCodeAt()–>Unicode編碼,不超過\uFFFF
  • .codePointAt()–>Unicode2編碼,超過\uFFFF

編碼變字元串

  • String.fromCharCode()
  • String.fromCodePoint()
var ss = "?";
console.log(ss.length);
console.log("?:" + ss.charCodeAt(0).toString(16));//\ud83d
console.log("?:" + ss.charCodeAt(1).toString(16));//\udc12
console.log("\ud83d\udc12");  // utf-16
//  擷取unicode2編碼
console.log("?:" + ss.codePointAt(0).toString(16));//\u1f412
console.log("?:" + ss.codePointAt(1).toString(16));//\udc12
           

周遊

for…of

for(let c of 'world')
{
	console.log(c);
}
           

查找子字元串

  • indexOf([子字元串],[n開始查詢位置])
  • includes()–>true:子字元串存在,false:不存在
  • startsWith()–>是否以指定子字元串開頭
  • endsWith()–>是否以指定子字元串結尾
//  ES5:indexOf
console.log("abcde".indexOf("cd",0));

// ES6
console.log("hello world".includes("w"));
console.log("hello world".startsWith("hello"));
console.log("hello world".endsWith("world1"));
           

重複輸出

.repeat()–>

  • 如果參數是浮點數,會将浮點數轉換為整數,向下取整
  • 如果參數是負數或Infinity,抛出異常
  • 特例:0到-1之間的浮點數,等同于0
  • 如果參數是字元串,将字元串轉換為數值型
  • 如果參數值無法轉換為數值,等同于0
console.log("hello".repeat(10));
console.log("h".repeat(0));  // 輸出長度為0的字元串
console.log("wow".repeat(2.9));  //wowwow   2.9 => 2
console.log("ha".repeat(-0.5));//0
console.log("ha".repeat(NaN)); //0
console.log("ok".repeat("4"));//okokokok
console.log("ok".repeat('ok'));//0
           

模闆字元串

增強型字元串,反單引号

  • 保留字元串的格式
  • 可大括号嵌入變量
  • 可大括号引用表達式計算
  • 可大括号引用函數
  • 大括号引用字元串依然是字元串
  • 大括号引用未定義變量異常
console.log(`hello
world`
);

var s = `你的名字是${name}`;
console.log(s);

var x = 20;
var y = 30;
console.log(`${x} + ${y} = ${x + y}`);

function fun()
{
	return "It's beautiful";
}
console.log(`wow ${fun()}`);

console.log(`Hello ${'world'}`);

//  引用模闆字元串本身
//  方法1:
let str1 = 'return ' + '`Hello ${name}`';
let func1 = new Function('name', str1);
console.log(func1("Mike"));
// 方法2:
let str2 = '(name)=>`Hello ${name}`';
let func2 = eval.call(null, str2);
console.log(func2('Mary'));
           
  • 标簽模闆–>fun``
function func1(s, n1, n2,n3)
{
   console.log(s);
   console.log(n1);
   console.log(n2);
   console.log(n3);
}
// 調用格式:函數名+模闆字元串
var n1 = 20;
var n2 = 30;
func1`abc${n1} xyz${n2}ok${n2 + n1}ddd`;
           
  • .raw``方法–>直接輸出最原始的字元串
console.log(String.raw`abc \n xyz`);
console.log(String.raw`abc \\n xyz`);
           

數值的擴充

二進制、八進制

從ES5開始,在嚴格模式中,八進制數值就不再允許使用字首0表示,ES6進一步明确了這一點,要使用0o。

  • 二進制:0b/0B
  • 八進制:0o/0O
var n1 = 345;
var n2 = 0o531;
if(n1 == n2)
{
       console.log("n1 == n2");
}
           

Number.infinite、Number.NAN

分别用來檢測Infinite(數值且有限)和NaN(非數值,不是數字)兩個值

傳統方法:

isFinite

isNaN

新方法與傳統的全局方法的差別:

  • 傳統方法先調用

    Number()

    将非數值轉換為數值,再進行判斷。
  • 新方法隻對數值有效,對于非數值一律傳回

    false

console.log(Number.isFinite(20));//true
console.log(Number.isFinite(0.4));//true
console.log(Number.isFinite(NaN));//false
console.log(Number.isFinite());//false
console.log(Number.isFinite(Infinity));//false
console.log(Number.isFinite('hello'));  // false
console.log(Number.isFinite(true));  // false

console.log("--------------");
console.log(Number.isNaN(NaN));  // true
console.log(Number.isNaN(15));   // false
console.log(Number.isNaN(9/NaN));  // true
console.log(Number.isNaN('false'/0)); // true;
// NaN:非數值
console.log(Number.isNaN('1'/'2')) ;// false
console.log(Number.isNaN('1'/'false')) ;// true
           

Number.parseInt、Number.parseFloat、Number.isInterger

// ES5的寫法
console.log(parseInt('44.66'));  // 舍去取整
console.log(parseFloat('65.34'));
// ES6的寫法
console.log(Number.parseInt('44.66'));  // 舍去取整
console.log(Number.parseFloat('65.34'));

console.log(Number.parseInt === parseInt);  // true
console.log(Number.parseFloat === parseFloat);  // true

// Number.isInteger用來判斷一個值是否為整數,20和20.0是同一個值
console.log(Number.isInteger(20)); // true
console.log(Number.isInteger(20.4));  // false
console.log(Number.isInteger(20.0));  // true
console.log(Number.isInteger("14")); // false
console.log(Number.isInteger(true)); // false
           

判斷浮點數相等Number.EPSILON

Number.EPSILON

極小值

if(((0.1 + 0.2) - 0.3) < Number.EPSILON)
{
	console.log("等于");
}
else
{
	console.log("不等于");
}
           

Math

  • .trunc:用于去除一個數的小數部分,傳回整數部分,正負号保留

    ​ 對于非數值,Math.trunc内部會使用Number方法将其轉換為數值

    ​ 對于空值和無法取整數的值,傳回NaN

    • .sign:用于判斷一個數到底是整數、負數、還是零

      ​參數為正數:傳回1

      ​參數為負數:傳回-1

      ​參數為0:傳回0

      ​參數為-0:傳回-0

      ​其他值:傳回NaN

  • .cbrt:用于計算一個數的立方根
  • .clz32:JavaScript的整數使用32位二進制形式表示

    ​ 方法傳回一個數的32位無符号整數形式有多少個前導0

    ​ 對于小數,隻考慮整數部分

    ​ 如果無法轉換為數值,傳回的是32,而不是NaN

  • .imul:傳回兩個數以32位帶符号整數形式相乘的結果,傳回的也是一個32位的帶符号整數

    ​ 如果兩個整數的乘積超過了2^53次方,JavaScript無法保證結果的精度

  • .fround:傳回一個數的單精度浮點數形式
  • .hypot:傳回所有參數的平方和的平方根
  • .expm1:傳回e^x-1
  • .log1p:傳回ln(1+x)
  • .log10:傳回以10為底的x的對數,如果x小于0,則傳回NaN
  • .log2:傳回以2為底的x的對數,如果x小于0,傳回NaN

數組的擴充

.from(對象轉換為數組)

兩類對象:

  • 類似數組的對象,即任何有length屬性的對象
  • 可周遊的對象(iterable),字元串,集合等
    以key作為數組的索引(從0開始),如果key不是按順序給出,那麼數組目前元素值是undefined,需要使用length定義數組的長度,如果長度比數組元素個數大,後面的值都是未定義,如果小,後面的值被忽略,對象中屬性順序可以颠倒
let obj1 = {
     '0':'hello',
     '2':'中國',
     '1':'world',
     '3':'ok',
     length:4
};

let array1 = Array.from(obj1);
console.log(array1);
           

Array.from方法還可以接受第二個參數,和map類似

console.log(Array.from([1,2,3,4],x=>x+x));
//  等同于下面的代碼
console.log(Array.from([1,2,3,4]).map(x=>x+x));

// 填充随機數數組
console.log(Array.from({length:20},()=>Math.random()));
           

.of方法(一組值轉換為數組)

console.log(Array.of(1,2,3,4,5));
console.log(Array.of(3));
console.log(Array.of(10).length);
           

.copyWithin(遷移數組元素)

target -->必選,遷移到哪兒

start -->必選,從哪兒開始遷移

end–>可選,哪兒結束遷移,預設到結尾

**[start,end)**遷移的内容,閉開區間

console.log([1,2,3,4,5,6].copyWithin(0, 3));//456456
console.log([1,2,3,4,5,6].copyWithin(1, 3,5));//145456
console.log([1,2,3,4,5,6].copyWithin(1, -3,-1));//145456
           

.find .findIndex(查找)

用來掃描數組,用來查找第一個滿足條件的數組元素

  • find

    傳回數組元素
  • findIndex

    傳回數組元素的索引
var n = [3,5,1,10,6].find((n)=>n>3)
console.log(n);
var m = [1,2,-4,6].find(function(value,index,arr){
   return value < 0
});
console.log(m);
var p = [1,2,-4,6].find((value,index,arr) => value < 0)
console.log(p);

var index = [1,2,-4,6].findIndex((value,index,arr) => value < 0)
console.log(index)
           

find和findIndex,可以接收第二個參數,用來綁定回調函數的this對象

和indexOf的差別:find和findIndex可以用來查找NaN

console.log([NaN].indexOf(NaN));
console.log([NaN].findIndex((n)=>Object.is(NaN,n)));
           

.fill(用給定的值填充數組)

常用來初始化數組

可選第二個第三個參數

console.log(new Array(10).fill(123));//[123,123,123......]
console.log(['x','y','z'].fill("hello world"));//["hello world","hello world","hello world"]
console.log(['1','2','3','5'].fill("xyz",1,3));//['1','xyz','xyz','5']
           

.entries、.keys(周遊)

  • .keys–>索引
  • .entries–>索引+值
for(let index of['a','b','c'].keys())
{
   console.log(index)//0 1 2
}

for(let [index,value] of['a','b','c'].entries())
{
   console.log(index) //0 1 2
   console.log(value) //a b c
}
           

數組的空位

沒有值

var arr1 = Array(3); // [,,,],應該是三個逗号而不是兩個
var arr2 = [,,,]
var arr3 = [undefined, undefined, undefined]
console.log(0 in arr2); // false,第0個位置是否有值
console.log(0 in arr3); // true
           
  • ES5:大多數情況下會忽略空位

forEach、filter、every和some都會跳過空位

map會跳過空位,但會保留這個值

join和toString會将空位視為undefined

undefined和null 會被處理成空字元串

  • ES6明确将空位轉為undefined
  • 擴充運算符(…)也會将空位轉為undefined
function fun(a,b,c)
{
   console.log(a);
   console.log(b);
   console.log(c);
}
var arr4 = ['x',,'y'];
fun(...arr4);//x undefined y
           
  • for…of也會将空位轉為undefined
let arr5 = ['a',,'b']
for(let i of arr5)
{
   console.log(i);//a undefined b
}
           

函數的擴充

函數的預設值

ES5判斷的方法

function write(x,y)
{
   //  無效的值:undefined、NaN、null、''(空串)
   y = y || 'JavaScript';//判斷y是否有效,若有效傳回第一個值
   console.log(x,y);
}
write('Hello', 'World');
write('Hello');//Hello JavaScript
write('Hello', NaN);//Hello JavaScript
write('Hello', null);//Hello JavaScript
write('Hello', '')//Hello JavaScript
           

ES5其他判斷函數參數預設值的方法

function write1(x,y)
{
   if(typeof y == 'undefined')
   {
      y = 'JavaScript'
   }
   console.log(x,y);
}
//  方法2
function write2(x,y)
{
   if(arguments.length == 1)
   {
      y = 'JavaScript'
   }
   console.log(x, y)
}
write1('Hello')//Hello JavaScript
write1('Hello', '')//Hello
write2('Hello')//Hello JavaScript
write2('Hello', '')//Hello
           

ES6預設值

function write3(x,y='JavaScript')
{
	console.log(x,y)
}
write3('Test');//Test JavaScript
write2('Test', '');//Test

 function Product(name='iPhone', price=6000)
{
  this.name = name;
  this.price = price;
}
var product = new Product()
console.log(product.name);
console.log(product.price);
           

參數預設值與解構指派預設值結合

function fun1({x,y=5})
{
     console.log(x,y)
}
//fun1();//報錯
fun1({});//undefined 5
fun1({x:10});//10 5
fun1({x:12,y:20});//12 20
           

解構指派預設值和函數參數預設值結合

function fun2(n, {value1='', value2=20})
 {
 	console.log(value2);
 }
 //fun2(123)  // error
 fun2(123,{});// 20
 fun2(123,{value2:100});// 100
           

兩種例子,有什麼差別

function f1({x = 0, y = 0} = {})
{
       console.log(x,y);
}

function f2({x, y} = {x:0, y:0})
{
       console.log(x,y);
}
f1()// 0 0
f2()// 0 0
f1({x:10, y:20})// 10 20
f2({x:10, y:20})// 10 20
f1({x:20}); // 20, 0  
f2({x:20}); // 20, undefined
f1({});  // 0 0
f2({});  // undefined undefined
f1({p:10});  // 0 0
f2({p:20});  // undefined undefined
           

參數預設值的位置與調用方式

不同位置,調用方法不同

// demo1
function fun1(x,y,z=20)
{
       console.log(z);
}
fun1(1,2,3);
fun1(1,2);//前兩個參數必須有

// demo2
function fun2(x, y=123,z = 200)
{
       console.log(y,z);
}
fun2(20);//第一個參數必須有
fun2(20,44);

// demo3
function fun3(x, y = 345, z)
{
       console.log(y);
}
fun3(20,123,100);//全部參數必須有
//fun3(20,,100);//報錯
           

length屬性(擷取無預設參數的個數)

length屬性從左向右檢測函數參數屬性,一旦遇到第一個有預設值的參數,就會停止檢測,并傳回第一個帶有預設值參數前面參數的個數

console.log((function(x){}).length)//1
console.log((function(x = 10){}).length)//0
console.log((function(x,y=100,z,t){}).length) // 1
console.log((function(x,y,z=100){}).length) // 2
console.log((function(x,...args){}).length) // 1
           

函數參數的作用域

var x = 10;
function fun1(m, y = x)
{
     console.log(y);
}
fun1(100);//100

/*  error
function fun2(m, y = n)
{
     let n = 90;
     console.log(y);//n未定義錯誤
} */
// fun2(100)

//預設值為函數
var value = 'hello';
function fun3(value = 'xyz', func = x=>value){
console.log(func());
}
fun3()//xyz
           

參數預設值應用

function throwIfMissing()
{
   throw new Error('必須指定該參數值');
}
function fun1(x,y = throwIfMissing())
{
   console.log(y);
}
fun1(10);//抛出異常-->必須指定該參數值

 function fun2(x, y = undefined)
 {
   console.log(y)//undefined
 }
           

rest參數

放在函數參數最後面,指任意多個參數

function add(...values)
{
   let sum = 0;
   for(var v of values)
   {
      sum += v;
   }
   return sum;
}
console.log(add(1,4,5,7))

function fun1(a,b,...c)
{
	console.log(a,b);
	for(var v of c)
	{
		console.log(v)
	}
}
fun1(1,2,3,4)
           

arguments和rest參數的差別:

  • arguments不是數組
  • rest參數是數組
function sort1()
{
   const sortedNumbers = Array.prototype.slice.call(arguments).sort();//不能直接調用sort()
   return sortedNumbers;
}
console.log(sort1(3,2,1))

function sort2(...numbers)
{
   const sortedNumbers = numbers.sort();//能直接調用sort()
   return sortedNumbers;
}
console.log(sort2(5,4,3));
           

參數長度差別:

  • 整個函數的參數是形參,不考慮rest參數和帶預設值的參數
  • arguments考慮的是值參
function fun2(a,b,c,d,...e)
{
   // arguments考慮的是值參
   console.log(arguments.length)  // 10
}
fun2(1,2,3,4,5,6,7,8,9,10)

console.log((function (a,b,c,d,...e)
{
   console.log(arguments.length)  // 10
}).length)  // 4  考慮的是形參,但不包括rest參數和帶預設值的參數
           

擴充運算符

...

三個點表示,将數組變成單值

var values = [1,2,3,4];
console.log(...values);// 1 2 3 4
console.log(values);//[1,2,3,4]
console.log('abc','xyz',...values,30);
           

對于rest參數,調用時隻能傳遞多個單值,不能傳遞數組

function addNumbers(...numbers)
{
   var sum = 0;
   for(var n of numbers)
   {
      sum+=n;
   }
   return sum;
}
var arr = [1,4,7,12,-3];

// console.log(addNumbers(arr))  // error 對于rest參數,隻能傳遞多個單值,不能傳遞數組
console.log(addNumbers(...arr))  // ok
           

擴充運算符應用

合并數組

var arr1 = ['a','b']
var arr2 = ['c','d']
var arr3 = ['e','f']

// ES5
var arr4 = arr1.concat(arr2, arr3);
console.log(arr4);

// ES6
var arr5 = [...arr1, ...arr2, ...arr3]
console.log(arr5)
           

擴充運算符與解構指派結合

rest必須是數組的最後一個元素

const [a,...b] = [1,2,3,4];  // ...b rest參數
console.log(a);
console.log(...b);

const [first, ...rest] = [];
console.log(first);  // undefined
console.log(rest);   // []

const [x, ...y] = [10];
console.log(x)//10
console.log(y)//[]

//const [...k, t] = [10,20] // error rest必須是數組的最後一個元素
           

函數的傳回值

function fun1()
{
     return [1,2,3,4]
}

function fun2(a,b,c,d)
{
     console.log(a,b,c,d)
}
var values = fun1();
fun2(...values)  //  可以通過擴充運算符将函數傳回的數組解析成多個值
           

字元串

字元串拆成數組

var str = "hello";
var strArray = [...str]; // 不能是...str
console.log(strArray);
           

正确識别32位Unicode

console.log('a\uD83D\uDE80b'.length);  // 4,無法獲得真正的字元串長度
console.log([...'a\uD83D\uDE80b'].length);//3,真正的長度
console.log([...'a\uD83D\uDE80b'][1]);//小火箭圖示

 function length(str)
 {
    return [...str].length
 }
 console.log(length([...'a\uD83D\uDE80b']));//3
 
  var str = 'a\uD83D\uDE80b';//\uD83D\uDE80b-->小火箭
  console.log(str);//a小火箭
  var str1 = str.split('').reverse().join('');
  console.log(str1);//翻轉失敗,buDE80\uD83D\a'
  var str2 = [...str].reverse().join('');
  console.log(str2);//小火箭a,正确翻轉
		   
           

可以處理類似數組的對象(Iterator)

let map = new Map(
   [
       [1, 'one'],
       [2, 'two'],
       [3, 'three']
   ]
);
let arrKeys = [...map.keys()];
console.log(...arrKeys);//1 2 3
let arrValues = [...map.values()];
console.log(...arrValues);//one two three
           

name屬性

傳回該函數的屬性名

function myfun(){}
console.log(myfun.name);

var fun1 = function(){}//匿名函數
// ES5是空串,ES6是變量名
console.log(fun1.name) // fun1

var fun2 = function process(){}
console.log(fun2.name); // process

console.log((new Function).name) // anonymous
function test(){}
console.log(test.bind({}).name) // bound test,前置bound
           

箭頭函數

使用=>定義的函數

  • =>的左側表示函數的參數,右側表示函數體
  • 如果=>右側隻有一條語句,該條語句将作為return的參數
var f = x => x;
console.log(f(100))
//  相當于
var f1 = function(x)
{
   return x;
}

 //  如果箭頭函數的參數沒有,或有多個參數
 var f2 = ()=> 123;
 f2 = function()
 {
    return 123;
 }
 
  // 箭頭函數和變量結構結合使用
 var f5 = ({first, last}) => first + '.' + last;
 var person = {first:'Bill', last:'Gates'};
 console.log(f5(person));
 //相當于
 function f6(person)
 {
    return person.first + '.' + person.last;
 }
 console.log(f6(person));
           

可以簡化回調函數

var arr2 = [1,2,3,4,5].map(x=>x*x)
console.log(...arr2)
var result2 = values.sort((a,b)=>a-b);
console.log(...result2)
           

rest參數也可以用于箭頭函數

var fun = (...nums)=>nums; // rest參數也可以用于箭頭函數
console.log(fun(1,2,3,4,5,6))
           

對象的擴充

屬性和方法簡潔表示

屬性定義時

// ES5
var obj1 = {name:'Bill'};
console.log(obj1.name)

// ES6
var name = 'Mike';
var obj2 = {name};
console.log(obj2.name);
           

傳回值時

// ES6中的傳回方式
function fun1(x,y,z)
{
   return {x,y,z};
}

// ES5中的傳回方式
function fun2(x,y,z)
{
   return {x:x,y:y,z:z};
}
console.log(fun1(1,2,3))
console.log(fun2(4,5,6));
           

方法定義時

// ES5:方法的傳統表示法
var obj3 = {
   process:function()
   {
      return "I love you."
   }
};

// ES6:方法的簡潔表示法
var obj4 = {
   process()
   {
      return "How are you?"
   }
}
console.log(obj3.process());
console.log(obj4.process());
           

簡潔定義示例

let price = 7000;
var product =
{
   productName:'iPhone7 Plus',
   price,
   buy(name, currentPrice)
   {
      console.log('已經購買');
      return {name, currentPrice,discount:price - currentPrice};
   }
}
console.log(product.productName);
console.log(product.buy('John', 6500));
           

屬性名表達式

ES5,有三種向對象中添加屬性的方法,其中兩種是動态的,一種是靜态的

//  靜态添加屬性
var obj1 = {
   name:'Bill'
};
//  動态一
obj1.age = 30;
//  動态二
obj1['salary'] = 2000;
           

使用表達式

obj1['hello' + 'world'] = 'hello world';
const p = 'x';
obj1[p+p] = 'xx';
console.log(obj1.name)
console.log(obj1.age)
console.log(obj1.salary)
console.log(obj1.helloworld);
console.log(obj1.xx);
           

ES6,定義時方括号内使用表達式

var obj2 = 
{
   name:'Mike',
   ['product' + 1]:'iPhone8'
}
console.log(obj2.product1);

var y = 'hello';
var obj3 = 
{
  [y]:'world' //  如果要解析變量,需要使用方括号
};
console.log(obj3.hello)

var obj4 = 
{
  ['hello']:'hello world' // ok,如果屬性名直接是一個字元串值,可以不适用方括号
  //  當然,使用方括号也可以
}
console.log(obj4.hello)
           

方法名也可以

let obj5={
   ['pro' + 'cess']()
   {
      console.log('process')
   }
};
obj5.process();
           

屬性名表達式和簡潔表示法不能同時使用,否則會報錯

var name1 = 'kkk';
/* error
 * var obj6 = {
   [name1]
 }*/
 var obj6 = {
   [name1]:'xxx'  // 屬性名使用表達式,右側必須指定屬性值
 }
 console.log(obj6.kkk)
           

Object.is

console.log(Object.is('abc', 'abc'));
console.log(Object.is({},{}));
           

Object.is方法

  • +0和-0不相等
  • NaN等于自身
console.log(+0 === -0);   // true
console.log(Object.is(+0, -0));  // false

console.log(NaN === NaN);  // false
console.log(Object.is(NaN, NaN)); // true
           

Object.assign

複制對象屬性,assign參數有兩個:

  • targetObject
  • …rest參數(sourceObject)
var target = {id:10};
var source1 = {name:'iPhone'};
var source2 = {price:7000};
var source3 = {location:'中國'};
Object.assign(target, source1,source2,source3);//将source1 2 3屬性添加到target
console.log(target);
           

若有重複屬性,後面的覆寫前面的

target = {a:1};
 source1 = {a:20};
 source2 = {b:30};
 source3 = {b:50};
 Object.assign(target, source1, source2, source3);//a 20 b 50
// Object.assign(target, source1, source3, source2);//a 20 b 30
 console.log(target);
           

有嵌套式時,直接覆寫,隻複制自身的屬性

var target = {a:{b:'c',d:'e'}};
var source = {a:{b:'ok'}};
Object.assign(target, source);// a {b:'ok'}
console.log(target);
           

不可枚舉的屬性和繼承的屬性不會複制, 不可枚舉:enumerable = false

var target = {a:'a'};
var source = Object.defineProperty({b:'b'},'name',{
   enumerable:false,
   value:'Bill'
});
console.log(target)
console.log(source)
Object.assign(target, source)//a b
console.log(target)
           

可以複制數組

var targetArray = [1,2,3];
var sourceArray = [4,5];
Object.assign(targetArray,sourceArray)
console.log(targetArray);  // [4,5,3]
           

應用

為對象添加屬性

class MyClass
{
   constructor(x,y)
   {
      Object.assign(this, {x,y})
   }
}
var my = new MyClass(10,20);
console.log('x', my.x);
console.log('y', my.y);
//  方法二
Object.assign(my, {name:'Bill'});
console.log('name', my.name);
//  方法三,類為原型添加
Object.assign(MyClass.prototype, {price:1200});
console.log('price', my.price);
           

為對象添加方法

Object.assign(MyClass.prototype, {
   add(x,y)
   {
      return x + y;
   }
});
console.log('add', my.add(10, 40));
           

克隆對象

function cloneObject(origin)
{
   return Object.assign({},origin);
}
var origin = {id:20, name:'Bill'};
console.log('origin',cloneObject(origin));
           

合并多個對象

var target = {a:10}
var source1 = {b:20}
var source2 = {c:30}
var source3 = {d:40}
const merge = Object.assign(target, source1, source2, source3);
console.log(merge)
           

為屬性指定預設值

const DEFAULTS = 
{
   name:'Mike',
   price:2000
}
function process(options)
{
   var obj = {}
   Object.assign(obj, DEFAULTS, options)
   return obj;
}
console.log(process({a:20,price:3000}))//a 20 name Mike price 3000
           

Symbol

為了解決對象屬性沖突問題,第7種資料類型

//let s = Symbol(); // s就是獨一無二的
let s = Symbol("s"); // s就是獨一無二的
console.log(typeof s);//Symbol
let ss = Symbol("ss");
console.log(typeof ss);//Symbol
console.log(s);//Symbol(s)
console.log(ss);//Symbol(ss)
           

獨一無二

let aa = Symbol("x");
let bb = Symbol("x");
if(aa === bb)
{
       console.log("aa === bb");
}
else
{
       console.log("aa != bb");
}
//"aa != bb
           

Symbol不能與其他類型的值進行運算,否則會報錯

不能隐式轉換到字元串

可以顯式轉換為String

var sym = Symbol('Test Symbol');
//var sss = "hello" + sym;  //  不能隐式轉換到字元串
var str = String(sym);  //  方法1
console.log(str)
console.log(sym.toString());  //  方法2
           

Symbol可以轉為布爾值,但不能轉為數值

var sym1 = Symbol("abc");
console.log(Boolean(sym));  //  true
           

應用

作為屬性名

var id = Symbol(); 
var name1 = Symbol();
var obj1 = {};
//  方法1
obj1[id] = 200;
obj1[name1] = 'Mike';

// 方法2
var obj2 = {
      [id]:300  //  如果不加方括号,name屬性名的類型就是String
      //  而不是Symbol              
};

// 方法3
var obj3 = {};
Object.defineProperty(obj3, name1, {value:'John'});

console.log('obj1 id', obj1[id]);//200
           
  • Symbol不能使用點(.)運算符
  • 對象内部也需要使用方括号定義屬性
let sym = Symbol();
let obj = {
       //  Symbol屬性必須加方括号,否則就變成了字元串屬性了
       [sym]:function(){console.log('hello world')}
};
obj[sym]();
           

定義常量

let CONST ={
       DEBUG:Symbol('debug'),
       INFO:Symbol('info'),
       WARNING:Symbol('warning')
};
console.log(CONST.INFO.toString());

const COLOR_WHITE = Symbol('white');
const COLOR_RED = Symbol('red')
           

周遊

getOwnPropertySymbols,隻Symbol屬性

var obj = {name:'Bill'};
var a = Symbol('a');
var b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols)
           

for in非Symbol屬性

for(var key in obj)
{
       console.log(key);
}
           

Reflect.ownKeys,同時擷取Symbol和非Symbol屬性

console.log(Reflect.ownKeys(obj));
           

可以利用Symbol的特性為對象添加一些非私有的,但又希望 隻用于内部的方法或屬性

var size = Symbol('size');
class Collection
{
       constructor()
       {
             this[size] = 0; //  為對象添加一個Symbol類型的名為size的屬性,初始值為0
       }
       add(item)
       {
              this[this[size]] = item;
              this[size]++;
       }
       static sizeOf(instance)
       {
              return instance[size];
       }
}
var cc = new Collection();
console.log(Collection.sizeOf(cc));//0

cc.add('ok');//0 ok
cc.add("xyz");//1 xyz
console.log(Collection.sizeOf(cc));//2
console.log(Object.keys(cc));//["0","1"]

console.log(Object.getOwnPropertySymbols(cc))//[Symbol('size')]
           

.for與.keyFor方法

Symbol.for首先會向全局環境注冊名為key的Symbol變量

第一次調用for方法,會傳回一個新的Symbol變量,如果以後再調用for方法,并且for方法的參數值(key)在全局環境中存在,那麼for方法會傳回這個已經注冊的Symbol變量

var name1 = Symbol("name")
var name2 = Symbol("name")
console.log("name1 === name2", name1 === name2);//false
 var id1 = Symbol.for("id") 
 var id2 = Symbol.for("id")
 console.log("id1 === id2", id1 === id2);//true
           

keyFor:傳回某個已經注冊的Symbol變量的key

console.log(Symbol.keyFor(id1));//id
console.log(Symbol.keyFor(name1));//未定義,因為name1并沒有全局環境注冊
console.log(Symbol.keyFor(id2));//id
           

Proxy

用于攔截對象的操作

var obj1 = {name:'Bill', 
            process:function()
            {
               //console.log('----')
               console.log('process Bill')
            }
           };
var obj2 = {name:'Mike', 
            process:function()
            {
               //console.log('----')                     
               console.log('process Mike');   
            }
           };
console.log(obj1.name);
obj1.process();
console.log(obj2.name);
obj2.process();
           

var obj = new Proxy(target, handler);

target:要攔截的對象

handler:攔截的動作,取代原來的方法,要想以前功能依然在,使用Reflect.method

var obj = new Proxy(obj1, {
   get:function(target, key,receiver)
   {
      console.log('----');
      return Reflect.get(target, key, receiver);
   }
}); 
console.log(obj.name);
obj.process();
           

用代理攔截多個對象,單獨将handler提出來

var handler = {
   get:function(target, key,receiver)
   {
      console.log('----');
      return Reflect.get(target, key, receiver);
   }          
}
var proxyObj1 = new Proxy(obj1, handler);
var proxyObj2 = new Proxy(obj2, handler);
console.log(proxyObj1.name);
proxyObj1.process();
console.log(proxyObj2.name);
proxyObj2.process();
           

讀取攔截get

攔截屬性的讀取操作

var product = {
    	    name:'iPhone'
    };
    //讀取不存在的屬性抛出異常
    var proxy = new Proxy(product, {
    	  get:function(target, property)
    	  {
    	  	if(property in target)
    	  	{
    	  		return target[property];
    	  	}
    	  	else
    	  	{
    	  		throw new ReferenceError("屬性 \"" + property + "\"不存在!");
    	  	}
    	  }    
    });
    console.log(proxy.name);
           

get代理的繼承

let proto = new Proxy({},{
     get(target, propertyKey,receiver)
     {
      console.log('GET ' + propertyKey);
      return target[propertyKey];
     }
});
let obj = Object.create(proto);
obj.name;
           

應用:利用get代理讀取數組的負索引

var arr1 = [1,2,3];
//console.log(arr1[-1]);
//  arr1[0] = arr1[-3]   arr1[2] = arr1[-1]

function createSuperArray(...elements)
{
       let handler  = {
            get(target, key, receiver)
            {
             let index = Number(key);
             if(index < 0)
             {
                key = String(target.length + index)//-1-->length-1
             }
             return Reflect.get(target, key,receiver);
            }
       }
       let target = [];
       target.push(...elements);
       return new Proxy(target, handler);
};

let arr2 = createSuperArray(1,2,3);
console.log(arr1[1]);//2
console.log(arr1[-1]);//未定義
console.log(arr2[1]);//2
console.log(arr2[-1]);//3
console.log(arr2[-2]);//2
console.log(arr2[-3]);//1
           

寫操作攔截set

校驗屬性值

var product = {
    	 name:'特斯拉汽車',
    	 price:80  // 30-180        	
    };
    
    let validator = {
    	   set:function(obj, key, value)
    	   {
    	   	// 首先判斷屬性名
    	   	if(key == 'price')
    	   	{
    	   		if(!Number.isInteger(value))
    	   		{
    	   			throw new TypeError('價格必須是整數');
    	   		}
    	   		if(value < 30 || value > 180)
    	   		{
    	   			throw new RangeError('價格必須在30到180之間');
    	   		}
    	   	}
    	   	obj[key] = value;
    	   }
    };
    let product1 = new Proxy(product, validator);
  //  product1.price = 200;
    product1.price = 170;
    console.log(product1.price);
   // product1.price = 'abc';
           

控制屬性是否可通路

​ 對象的内部屬性 屬性名以下劃線(_)開頭

var handler = {
      get(target, key)
      {
          invariant(key,'get');
          return target[key];
      },
      set(target, key,value)
      {
          invariant(key,'set');
          return true;
      }
  };
  function invariant(key,type)
  {
       if(key[0] == "_")
       {
              throw new Error(`内部屬性不能被通路 ${type}`);
       }
       
  };
  var obj = {
   name:'Bill',
   _value:20
   
  };
  var objProxy = new Proxy(obj, handler);
  console.log(objProxy.name);
 // console.log(objProxy._value);
 objProxy.name = "Mike";
// objProxy._value = 40;
           

攔截函數的調用:call和apply

改變函數傳回值

var fun1 = function(){return '世界您好!';}
var handler = {
   apply:function()
   {
      return 'hello world';
   }
};
console.log(fun1());//世界您好!
var funProxy = new Proxy(fun1, handler);
console.log(funProxy());//hello world
           

修改原傳回值

function sum(num1, num2)
{
       return num1 + num2;
}
console.log(sum(20,40));//60

var twice = {
   apply(target, ctx, args)
   {
      return Reflect.apply(...arguments) * 2;
   }
};
var sumProxy = new Proxy(sum, twice);
console.log(sumProxy(20,40));//120
console.log(sumProxy.call(null, 20,40));//120
console.log(sumProxy.apply(null, [30,50]));//160
           

攔截in操作has

隐藏屬性,隻影響in判斷

var handler = {
   has(target, key)
   {
      //console.log("<" + key + ">");
      if(key[0] == '_')
      {
         return false;
      }
      return key in target;
   }
};

var obj = {name:'iPhone7', price:5800, _value:200};
var objProxy = new Proxy(obj, handler);
console.log('_value' in obj);  // true

console.log('_value' in objProxy);  // false

console.log('price' in obj);  // true

console.log('price' in objProxy);  // true
           

has方法并不會影響for in操作

for(key in objProxy)
{
   console.log(key)
}
           

如果原對象不可配置或禁止擴充,那麼這時has攔截會報錯

var obj1 = {a:20};
Object.preventExtensions(obj1);
var proxy = new Proxy(obj1, {
     has:function(target, key)
     {
      return false;
     }
});
//  'a' in proxy //抛出異常
           

攔截new指令contrust

var handler = {
  construct(target, args)
  {
   console.log('construct');
   return new target(...args);
  }
};
var proxy = new Proxy(function(){}, handler);
new proxy();//contrust
           

傳回的不是對象,抛出異常

var handler1 = {
  construct(target, args)
  {
   console.log('construct');
   return 20;
  }
};
var proxy1 = new Proxy(function(){}, handler1);
//new proxy1(); //抛出異常
           

可以傳回别的對象

var handler2 = {
  construct(target, args)
  {
   console.log('construct');
   return {name:'Bill'};
  }
};
var proxy2 = new Proxy(function(){}, handler2);
console.log(new proxy2().name);
           

攔截delete操作deleteProperty

var handler = {
  deleteProperty(target, key)
  {
   delete target[key];  //  需要在該方法中再次删除對象屬性
   console.log('删除了' + key + "屬性");
   return true;
  }
};
var obj = {name:'Bill', age:40};
var objProxy = new Proxy(obj, handler);
console.log(objProxy.age);
delete objProxy.age;
console.log(objProxy.age);
           

攔截define操作defineProperty

攔截動态添加屬性

var obj = {};
      var handler = {
       defineProperty(target, key, descriptor)
       {
          console.log("<" + key + ">")  
            //  要想讓被攔截的操作仍然發揮原來的作用,需要調用
            // Reflect.method
            // 否則defineProperty會進入遞歸
          return Reflect.defineProperty(target, key, descriptor);
       }
      }
var proxy = new Proxy(obj, handler);
proxy.name = "Bill";
console.log('name' in proxy);
           

Reflect

将Object對象的一些明顯屬于語言層面的方法放到Reflect中

Object和Reflect的方法是一樣的,以後再部署語言層面的方法會隻部署在Reflect中

defineProperty、deleteProperty、has

Generator

是個狀态機,并延後執行 ,不會立刻執行

普通函數加*,一個yield表示一個狀态

function* helloworldGenerator()
{
   console.log('第一次執行');
   yield 'hello';   //  一個yield表示一個狀态
   console.log('第二次執行');
   yield 'world';   
   console.log('第三次執行');             
   return 'ending'; // 非必須,也表示一個狀态,最後一個狀态
}
var hw = helloworldGenerator(); // 傳回一個周遊器對象,狀态的集合

var obj = hw.next();  // 狀态切換,next傳回一個普通對象
console.log(obj); // value-hello done-false
console.log(obj.value); // hello
console.log('-------------------------------');

obj = hw.next();  // 狀态切換,next傳回一個普通對象
console.log(obj); // value-world done-false
console.log(obj.value);
console.log('-------------------------------');

obj = hw.next();  // 狀态切換,next傳回一個普通對象
console.log(obj); // value-ending done-true
console.log(obj.value);
console.log('-------------------------------');    

obj = hw.next();  // 狀态切換,next傳回一個普通對象
console.log(obj); // value-undefined done-true
console.log(obj.value); // undefined
console.log('-------------------------------');    
           

yield

function sum(...values)
{
   var n = 0;
   for(let v of values)
   {
      n += v;
   }
   console.log('sum');
   return n;
}
 function* gen()
{
  yield 10 + 20;
  yield 10 * sum(1,2,3,4);          	
}
var obj = gen();
console.log(obj.next());
console.log(obj.next());
           

Generator函數可以不用yield,Generator就變成了暫緩執行的函數

function* fun()
{
   console.log('fun');
}
var obj1 = fun();
obj1.next();
           

yield不能用于普通函數,否則會抛出異常

function process()
{
   // yield 'abc';
}
           

yield用在一個表達式中,必須放在圓括号裡

function* gen1()
{
  // console.log('Hello' + yield 'world');
  console.log('Hello' + (yield 'world'));
}
var obj2 = gen1();
obj2.next();// fun,暫停了
obj2.next();// helloundefined
           

yield語句用于函數參數或用于指派表達式的右邊,可以不加圓括号

function* gen2()
{
   sum(yield '1', yield '2');
   var input = yield 20;
}
gen2().next();
           

next()

function* gen()
{
    var n = yield 20;
    console.log(n);
}
var obj = gen();
obj.next(); //暫停
obj.next(200);  // 需要給第二個next方法傳遞參數
           
  • next方法執行時,yield後面的變量值即next方法傳回的value值,next方法本身傳回done和value兩個屬性的對象,并可以通過傳值改變這屬性value的值。
  • next()傳的參數即此次yield語句的值
function* gen1()
{
   for(var i = 0; true;i++)
   {
      var reset = yield i;
      if(reset)
      {
         i = -1;
      }
   }
}
var obj1 = gen1();
console.log(obj1.next());//0 false
console.log(obj1.next());//1 false
console.log(obj1.next());//2 false
obj1.next(true);//重新開始,i=-1後加一       // 0 false
console.log(obj1.next());  // 1 false
console.log(obj1.next());  // 2 false
console.log(obj1.next());  // 3 false   
           

for of

切換狀态,并不包含return的傳回值

function* gen()
{
   yield 'a';
   yield 'b';
   yield 'c';
   yield 'd';
   return 'x';
}
//  a b c d
for(let v of gen())
{
   console.log(v);//a b c d
}
           

fibonacci數列

function* fibonacci()
{
   let [prev,curr] = [0, 1];
   for(;;)
   {
      [prev, curr] = [curr, prev + curr];
      yield curr;
   }
}

/*var obj = fibonacci();
console.log(obj.next().value);
console.log(obj.next().value);
console.log(obj.next().value);*/

for(let n of fibonacci())
{
    if(n > 1000) break;
    console.log(n);
}
           

for…of循環可以寫出周遊任何對象的方法。原生的JavaScript對象 沒有周遊接口

function* objectEntries(obj)
{
   let propKeys = Reflect.ownKeys(obj);//獲得對象所有屬性
   for(let propKey of propKeys)
   {
      yield [propKey, obj[propKey]];
   }
}

let arr = {name:'Bill', age:30};
for(let [key, value] of objectEntries(arr))
{
   console.log(`${key}:${value}`);
}
           

throw()

var g = function*()
{
   while(true)
   {
      try
      {
         yield;
      }
      catch(e)
      {
         if(e != 'a') throw e; //throw異常後結束generator函數
         console.log('Generator内部錯誤', e);
      }
   }
}

var obj = g();
obj.next();
try
{
   obj.throw('a');//内部 a
   obj.throw('b');//外部 b
}
catch(e)
{
   console.log('外部異常', e);
}
           

不用try catch,throw後直接跳出循環

var gg = function*()
{
   while(true)
   {
      yield;
      console.log('内部捕獲', e);
   }
};

var obj1 = gg();
obj1.next();
try
{
   obj1.throw('a');  // 外部異常a
   obj1.throw('b');  // 沒有執行
}
catch(e)
{
   console.log('外部捕獲', e);
}
           

應用:提前結束狀态

function fun(s)
{
   console.log(s + s);
}

function *gen()
{
   try
   {
      var a = yield fun('a');
      var b = yield fun('b');
      var c = yield fun('c');
   }
   catch(e)
   {
      console.log(e);
   }
}
var obj2 = gen();
obj2.next();// aa
//console.log(obj2.next());
obj2.throw('aaa');//aaa,提前結束
obj2.next();// 
obj2.next();
           

應用:函數内部抛出異常,如果内部沒有try catch,也會被外部異常捕獲

function *gen1()
{
   var x = yield 10;
   var y = x.toUpperCase();
   yield y;
}
var obj3 = gen1();
obj3.next();
try
{
    obj3.next(33);
}
catch(e)
{
   console.log(e);
}
           

return()

也是結束狀态,return方法傳回值與其參數相同

function *gen()
{
   yield 'a';
   yield 'b';
   yield 'c';
}

var obj = gen();
console.log(obj.next());   //  a
console.log(obj.return('hello world'));  // hello world
console.log(obj.next());   // undefined
           

如果有finally,return 會直接跳到finally的第一個yield語句,并傳回yield後面表達式的值。如果finally裡面沒有yield,則傳回 return的參數值

function* gen1()
{
   yield 'a';
   try
   {
      yield 'b';
      yield 'c';   // 不會執行
   }
   finally
   {
      yield 'd';
      yield 'e';
   }
   yield 'f';  //  不會執行
}
var obj1 = gen1();
console.log(obj1.next()); // 'a'
console.log(obj1.next()); // 'b'
console.log(obj1.return('hello')); // d
console.log(obj1.next()); // e,finally裡的yield必須都執行完
console.log(obj1.next()); // hello,參數值也要指行完
console.log(obj1.next()); // undefined,return已提前結束狀态
           

yield*與遞歸generator函數

遞歸函數:在函數内部調用函數自身

遞歸Generator函數:在Generator函數内部需要調用Generator函數自身

在Generator函數内部是否可以調用另外一個Generator函數?

在一個Generator函數中直接調用另外一個Generator函數是沒有任何作用的

function* gen1()
{
   console.log('x');
   yield 'a'; 
   console.log('y');
   yield 'b';
}
function* gen2()
{
   yield 1;
   yield* gen1();//相當于把代碼複制過來
   //yield gen1();
   yield 2;
}
var obj = gen2();
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
           

利用Generator遞歸函數枚舉嵌套數組中的所有值

const array = ['a', [1,2], ['b', [3, [4, 'c']],'d'],["abc","xyz"]];
function* enumerateArray(array)
{
       if(Array.isArray(array))
       {
              for(let i = 0; i < array.length;i++)
              {
                     yield* enumerateArray(array[i]);
              }
       }
       else
       {
              yield array;
       }
       
}
for(let x of enumerateArray(array))
{
       console.log(x);
}
           

将Generator函數作為對象屬性

//方法一
var obj1 = {name:'Bill', *gen(){yield 10}};
console.log(obj1.gen().next());// 10 false
console.log(obj1.gen().next());// 10 false

//方法二
var obj2 = {name:'Mike', gen:function*(){yield 'hello world'}};
console.log(obj2.gen().next());
           

this

ES6規定這個周遊器是Generator函數的執行個體,這個執行個體也繼承了Generator函數的prototype對象上的方法

function* gen()
{
   
}
gen.prototype.process = function()
{
       return 'test process';
}
let obj = gen();
console.log(obj instanceof gen);
console.log(obj.process());
           

不能像普通函數的this一樣在Generator函數中使用this

function* gen1()
{
       this.name = 'Bill';   //  沒有成功添加name屬性
}
let obj1 = gen1();
console.log(obj1.name);
           

可以采用變通的方式在Generator函數中為對象添加屬性

var genObj = {};
function* gen2()
{
       yield this.name = 'Bill';
       yield this.age = 30;
}
var g = gen2.bind(genObj)();
g.next();//調用三次才可
g.next();
g.next();
console.log(genObj.name);
console.log(genObj.age);

var genObj1 = {};
function* gen3()
{
        this.name = 'Bill';
        this.age = 30;
}
var g1 = gen3.bind(genObj1)();
g1.next();//調用一次即可
console.log(genObj1.name);
console.log(genObj1.age);
           

generator函數與狀态機

普通函數

var state = 1;
var stateMachine = function()
{
      if(state == 1)
      {
            console.log('狀态1');
            state++;
      }
      else if(state == 2)
      {
            console.log('狀态2');
            state++;
      }
      else 
      {
            console.log('狀态3');
            state = 1;
      }
}
stateMachine();
stateMachine();
stateMachine();
stateMachine();
stateMachine();
           

使用Generator函數切換狀态

var genStateMachine = function*()
{
       while(true)
       {
              console.log('狀态1');
              yield;
              console.log('狀态2');
              yield;
              console.log('狀态3');
              yield;
       }
}
var gen = genStateMachine();
gen.next();
gen.next();
gen.next();
gen.next();
gen.next();
           

Promise

Promise是一種異步實作模式,允許Promise中的任務在另一個線程中執行

Promise是一個構造函數,構造函數的參數可以傳遞一個回調函數,回調函數就是在另一個線程中執行的任務

成功或失敗兩種結果success和fail,成功Promise.then

以前就有,es6進行了api标準化

function timeout(ms)
{
       return new Promise((success,fail)=>{ //參數名随便起,回調函數
              setTimeout(success,ms);
       })
}
timeout(2000).then(()=>{
       console.log('success');
})
           
function timeout1(ms)
{
       return new Promise((success,fail)=>{
              if(ms % 2 == 0)
              {
                   setTimeout(success,ms);
              }
              else
              {
                      setTimeout(fail, ms);
              }
              
       })
}
timeout1(2000).then(()=>{
       console.log('success(2000)');
},()=>{
       console.log('fail(2000)');
});
timeout1(1999).then(()=>{
       console.log('success(1999)');
},()=>
{
       console.log('fail(1999)');
});
           

異步下載下傳HTML代碼

var getHtml = function(url)
{
       var promise = new Promise((success,fail)=>{
              var client = new XMLHttpRequest();
              client.open("GET", url);
              client.onreadystatechange = handler;
              client.responseType = 'text';
              client.setRequestHeader('Accept', "text/html");
              client.send();
              
              function handler()
              {
                     if(this.readyState != 4)
                     {
                             return;
                     }
                     if(this.status == 200)
                     {
                            success(this.response);
                     }
                     else
                     {
                            fail(new Error(this.statusText));
                     }
              }
       })
       return promise;
}

getHtml("basic.html").then((html)=>{
       console.log(html);
},(error)=>
{
       console.error(error);
})
           

then

then也可以有傳回值,是一個新的Promise對象,第二個then的參數是第一個then的傳回值

異步下載下傳HTML代碼

getHtml("basic.html").then((html)=>{
       console.log(html);   
       return html.length;
}).then((length)=>{
       console.log("html長度:" + length);
});
           

catch

異步下載下傳HTML代碼,統一捕獲

getHtml("basic.html").then((html)=>{
       console.log(html);
       return getHtml("then.html");
}).then((html)=>
{
       console.log(html);//第二次調用
}).catch((errorMsg)=>
{
       console.log(errorMsg);
})
           

異步操作與async函數

通過Generator函數與Promise對象封裝異步任務

Generator函數分段執行的,就是通過yield實作的可以在Generator函數中将大人物分解成多步,其中某些步驟
就可以交給Promise對象異步執行
這時Generator函數是暫停執行的,直到Promise對象執行完異步任務後,才會傳回Generator函數
核心問題:Generator函數與Promise對象之間的資料互動

主要的方式:Generator函數通過next方法傳回一個Promise對象,然後
Promise對象執行完異步任務後,将處理完的資料傳回給Generator函數繼續處理
           

demo:按txt方式下載下傳一個json文檔,然後通過Promise對象異步将該文檔轉換為json對象,再将json對象傳回給Generator函數,最後在Generator函數中輸出對象的所有屬性值

var getJSON = function(url)
{
       var promise = new Promise((success,fail)=>{
              var client = new XMLHttpRequest();
              client.open("GET",url);
              client.onreadystatechange = handler;
              client.responseType = 'text';
              client.send();
              function handler()
              {
                     if(this.readyState != 4)
                     {
                            return;
                     }
                     if(this.status == 200)
                     {
                            success(this.response);
                     }
                     else
                     {
                            fail(new Error(this.statusText));
                     }
              }
          
       })
       return promise;
}
function* gen()
{
       var url = "data.json";
       var result = yield getJSON(url);//
       console.log("name:" + result.name);
       console.log("age:" + result.age);
       console.log("salary:" + result.salary);
}
var g = gen();
//  Generator函數 -> Promise對象
var obj = g.next();
//  通過Promise對象将json文本轉換為json對象
obj.value.then((json)=>{
       var jsonObj = JSON.parse(json);
       return jsonObj;
}).then((jsonObj)=>{
       //  将解析完的JSON對象傳回gen函數(Promise對象 -> Generator函數傳遞資料的過程)
       g.next(jsonObj)
})
           

async

function timeout(ms)
{
       return new Promise((success,fail)=>{
              if(ms % 2 == 0)
              {
                  setTimeout(success, ms);
              }
              else
              {
                     //  必須調用fail,async函數和catch方法才會捕獲異常
                     fail(new Error("毫秒必須是偶數"));
              }
              
       })
}
timeout(2000).then(()=>{
       console.log('hello world 2000')
})
timeout(1999).catch((error)=>{
       console.log(error);
})
           

使用async函數,要es7,es6不支援

async function asyncPrint(value, ms)
{
        try
        {
          //  await等待success方法被調用,調用後,立刻往下執行
          await timeout(ms);
          console.log(value);
        }
        catch(e)
        {
          console.log(e);
        }
}
asyncPrint('hello world 3000', 3000);
asyncPrint('hello world 3000', 3001);
           

es5

function Product(name, price)
{
       this.name = name;
       this.price = price;
}
Product.prototype.toString = function()
{
       return 'name:' + this.name + ', price:' + this.price;
}

var product = new Product('iPhone8 Plus', 7000);
product.printName = function()
{
       console.log('商品名:' + this.name);
}
console.log(product.toString());
product.printName();
           

es6新方式

class NewProduct
{
       constructor(name, price)
       {
              this.name = name;
              this.price = price;
       }
       toString()
       {
              return 'name:' + this.name + ', price:' + this.price;
       }
       printName()
       {
              console.log('商品名:' + this.name);
       }
}
var newProduct = new NewProduct('特斯拉電動車', 900000);
console.log(newProduct.toString());
newProduct.printName();
           

新的定義類的方式就是一個文法糖

console.log(typeof NewProduct);  // function
           

枚舉類的屬性

console.log(Object.keys(NewProduct.prototype));  // [],在類中不在原型
console.log(Object.getOwnPropertyNames(NewProduct.prototype));  // ["constructor", "toString", "printName"];//方法在類原型屬性中
console.log(Object.keys(newProduct));  // ["name", "price"],屬性在執行個體對象本身
console.log(Object.getOwnPropertyNames(newProduct)); // ["name", "price"]

console.log(Object.keys(Product.prototype));  // ["toString"],es5傳統方法
console.log(Object.keys(product));  // ["name", "price", "printName"],es5傳統方法
           

構造方法(constructor)

類必須要有構造方法,如果未指定構造方法,系統會自動加一個沒有參數的構造方法

因為使用new指令建立對象時需要調用類中的構造方法

constructor預設會傳回當期類的執行個體對象,但也可以通過return傳回其他的對象

class MyClass
{
       constructor()
       {
               return new NewProduct('會員卡', 200);
       }
}
var p = new MyClass();
console.log(p.toString());
p.printName();
console.log(typeof p);
console.log(p instanceof NewProduct);//p屬于類NewProduct的執行個體
           
// name屬性
   class MyClass1{
       
   }
   var my = new MyClass1();
   console.log(MyClass.name);
//   console.log(my.name);   // undefined
           

class表達式定義類

函數表達式定義函數

var myFun = function()
 {
   console.log("myFun");
 }
 myFun();
 
 var myFun1 = function hello()//hello基本無用處
 {
   console.log(hello.name)
   console.log(typeof hello);
   console.log(typeof myFun1);
   console.log(myFun1.name);
 }
 myFun1();
 console.log(myFun1.name);
// hello();  error,隻在myFun1函數内部可以使用
           

類表達式

//不加Me,該class的name為MyClass
var MyClass = class Me
{
       getClassName()
       {
              console.log(Object.getOwnPropertyNames(MyClass));
              console.log(Object.getOwnPropertyNames(Me));
              return Me.name;
       }
}
var c = new MyClass();
console.log(c.getClassName()); //Me
console.log(MyClass.name); //Me
           
var Person = class
{
       constructor(name)
       {
              this.name = name;
       }
       printName()
       {
              console.log('姓名:' + this.name);
       }
}
new Person('Bill').printName();
           

繼承

class Person
{
       constructor(name, age)
       {
              this.name = name;
              this.age = age;
       }
       toString()
       {
              return 'name:' + this.name + ' age:' + this.age;
       }
}
class Teacher extends Person
{
       constructor(name, age, course)
       {
              super(name, age);
              this.course = course;
       }
       toString()
       {
              return super.toString() + ' course:' + this.course;
       }
}
var teacher = new Teacher('Bill', 30,'大資料');
console.log(teacher.toString());
           
  • 在子類的構造方法中必須調用super,否則建立類執行個體時會抛出異常
  • 因為子類中沒有this,是以要通過執行super方法來獲得this對象
class Teacher1 extends Person
{
       constructor(name, age, course)
       {
              super(name, age);//必須
              //this.course = course;
       }
       toString()
       {
              return super.toString() + ' course:' + this.course;
       }
}
var teacher1 = new Teacher1('Bill', 30,'大資料');
           
  • super的位置可以在構造函數随便放(Java必須在最前面),隻有調用super後,才能使用this
class Teacher2 extends Person
{
       constructor(name, age, course)
       {
              console.log('constructor');
              super(name, age);
              this.course = course;
       }
       toString()
       {
              return super.toString() + ' course:' + this.course;
       }
}
var teacher2 = new Teacher2('Bill', 30,'大資料');
           

原生構造函數的繼承

Boolean()、Number()、String()、Array()、Date()、Function()、RegExp()、Error()、Object()

ES5 不能繼承原生構造函數

ES6可以繼承原生構造函數

class NewArray extends Array{
       constructor(...args)
       {
              super(...args);
       }
}
var arr = new NewArray(1,2);
arr[2] = 'Bill';
arr[3] = 'Mike';
console.log(arr.length);  // 4

arr.length = 0;
console.log(arr[0]);   // undefined
           

過sum方法計算數組中所有數值類型元素的和

class SumArray extends Array
{
       constructor(...args)
       {
              super(...args);
       }
       sum()
       {
             var n = 0;
             for(let i = 0; i < this.length;i++)
             {
                if(typeof this[i] == 'number')
                {
                   n += this[i];
                }
             }
             return n;
       }
}
var sumArray = new SumArray(1,2,'abc',true,3,'hello', 4);
console.log(sumArray.sum());
           

類的getter與setter方法

getter和setter方法就是可以監控對象屬性讀寫的方法

class Person
{
       constructor()
       {
               this._name = '';
       }
       get name()
       {
              console.log('get name()');
              return '<' + this._name + '>';
       }
       set name(value)
       {
              console.log('set name(value)');
              this._name = value;
       }
}
var person = new Person();
person.name = 'Bill';
console.log(person.name);
           

Generator方法

利用Generator方法可以周遊對象中的資料

class MyClass
{
       constructor(...args)
       {
             this.args = args;
       }
       //  給類添加一個預設的周遊器
       *[Symbol.iterator]()
       {
             for(let arg of this.args)
             {
                yield arg;
             }
       }
       // 普通方法
       *gen()
       {
              for(let arg of this.args)
             {
                yield arg;
             }
       }
}
var my = new MyClass(1,2,3,4,5);
var obj = my.gen();
console.log(obj.next());//1
console.log(obj.next());//2
console.log(my[Symbol.iterator]().next());//1
//  x == arg
for(let x of new MyClass('a','b','c'))
{
       console.log(x);
}
           

類的靜态方法和靜态屬性

成員方法:必須通過類的執行個體調用

靜态方法:必須通過類本身調用

不能通過類執行個體調用,這一點和Java不同

成員方法會被繼承,而靜态方法不會被繼承

class MyClass
{
       // 會被繼承
       process()
       {
              console.log('process');
       }
       static delete()
       {
              console.log('delete');
       }
}
new MyClass().process();//類執行個體
//  new MyClass().delete();  類執行個體,抛出異常
MyClass.delete();//類本身
console.log('-----------------');
           

靜态方法可以從super對象調用

class SubClass extends MyClass
{
       static method()
       {
              console.log('method');
              super.delete();
       }
}
SubClass.method();
           

靜态屬性,并沒有直接支援方法,但可以處理一下

class NewClass
{
   
}
NewClass.name = 'Bill';
NewClass.name1 = 'Mike';
NewClass.age = 30;
console.log(NewClass.name);  // NewClass,而不是Bill,name屬性本身有,無法人工改變
console.log(NewClass.name1);
console.log(NewClass.age);
           

new.target屬性

使用new建立的執行個體,new才有target屬性,否則target為undefined

//  寫法1
  function Person1(name)
  {
       // 通過new指令建立對象
       if(new.target != undefined)
       {
              this.name = name;
       }
       else
       {
              throw new Error('必須使用new執行個體化類');
       }
  }
  // 寫法2
  function Person2(name)
  {
       if(new.target === Person2)
       {
              this.name = name;
       }
       else
       {
             throw new Error('必須使用new執行個體化類');
       }
  }
  var person1 = new Person1('李甯');
//  var person11 = Person1.call(person1, 'Bill');
           

new.target在類中傳回目前的Class

class MyClass
{
       constructor(name)
       {
             // console.log(new.target);
             // console.log(new.target === MyClass);
              this.name = name;
       }
}
new MyClass('John');
           

子類繼承父類,new.target傳回的是子類

class SubClass extends MyClass
{
       constructor(name)
       {
             super(name);
             console.log(new.target);
             console.log(new.target === MyClass);  // false
             console.log(new.target === SubClass);  // true
       }
}
new SubClass('李甯');
           

抽象類

可以利用new.target的特性模拟實作抽象類

  1. 抽象類不能直接執行個體化,必須通過子類繼承才能執行個體化
  2. 抽象類的抽象方法不能直接調用,必須在子類中覆寫後(override)才能調用
class AbstractPerson
 {
       constructor()
       {
              if(new.target === AbstractPerson)
              {
                     throw new Error('抽象類不能執行個體化');
              }
       }
       // 抽象方法
       print()
       {
              // 内部抛出異常
              throw new Error('抽象方法不能直接調用.')
       }
       //  普通方法
       method()
       {
              console.log('method:普通方法');
       }
 }
 class Teacher extends AbstractPerson
 {
       constructor()
       {
              super();
       }
       //  override父類的抽象方法
       print()
       {
              console.log('Teacher.method');
       }
 }
 class Engineer extends AbstractPerson
 {
       constructor()
       {
              super();
       }
 }
 
 new Teacher().print();
 // new Engineer().print(); // error,調用了抽象類的print方法
// new AbstractPerson();
 new Teacher().method(); // 調用普通方法