ES6新特性:Javascript中的Reflect對象

  Reflect介紹:

  Reflect這個對象在個人node(v4.4.3)中尚未實現, babel(6.7.7)也沒有實現 ,新版本的chrome是支持的, ff比較早就支持Proxy和Reflect了,要讓node支持Reflect能夠安裝harmony-reflect ;node

  Reflect不是構造函數, 要使用的時候直接經過Reflect.method()調用, Reflect有的方法和Proxy差很少, 並且多數Reflect方法原生的Object已經從新實現了。git

  什麼要使用Reflect

  這裏列舉幾個爲何要使用Reflect的緣由, 譯文地址:Reflect , 大概翻譯了一遍:github

  1:更加有用的返回值: Reflect有一些方法和ES5中Object方法同樣樣的, 好比: Reflect.getOwnPropertyDescriptor和Reflect.defineProperty,  不過, Object.defineProperty(obj, name, desc)執行成功會返回obj, 以及其它緣由致使的錯誤, Reflect.defineProperty只會返回false或者true來表示對象的屬性是否設置上了, 以下代碼能夠重構:chrome

try {
  Object.defineProperty(obj, name, desc);
  // property defined successfully
} catch (e) {
  // possible failure (and might accidentally catch the wrong exception)
}

  重構成這樣:數組

if (Reflect.defineProperty(obj, name, desc)) {
  // success
} else {
  // failure
}

  其他的方法, 好比Relect.set , Reflect.deleteProperty, Reflect.preventExtensions, Reflect.setPrototypeOf, 均可以進行重構;微信

  2:函數操做,  若是要判斷一個obj有定義或者繼承了屬性name, 在ES5中這樣判斷:name in obj ; 或者刪除一個屬性 :delete obj[name],  雖然這些很好用, 很簡短, 很明確, 可是要使用的時候也要封裝成一個類;babel

  有了Reflect, 它幫你封裝好了, Reflect.has(obj, name),  Reflect.deleteProperty(obj, name);app

  3:更加可靠的函數式執行方式: 在ES中, 要執行一個函數f,並給它傳一組參數args, 還要綁定this的話, 要這麼寫:ide

f.apply(obj, args)

  可是f的apply可能被從新定義成用戶本身的apply了,因此仍是這樣寫比較靠譜:函數

Function.prototype.apply.call(f, obj, args)

  上面這段代碼太長了, 並且很差懂, 有了Reflect, 咱們能夠更短更簡潔明瞭:

Reflect.apply(f, obj, args)

  4:可變參數形式的構造函數: 想象一下, 你想經過不肯定長度的參數實例化一個構造函數, 在ES5中, 咱們可使用擴展符號, 能夠這麼寫:

var obj = new F(...args)

  不過在ES5中, 不支持擴展符啊, 因此, 咱們只能用F.apply,或者F.call的方式傳不一樣的參數, 惋惜F是一個構造函數, 這個就坑爹了, 不過有了Reflect, 咱們在ES5中可以這麼寫:

var obj = Reflect.construct(F, args)

  5:控制訪問器或者讀取器的this: 在ES5中, 想要讀取一個元素的屬性或者設置屬性要這樣:

var name = ... // get property name as a string
obj[name] // generic property lookup
obj[name] = value // generic property update

  Reflect.get和Reflect.set方法容許咱們作一樣的事情, 並且他增長了一個額外的參數reciver, 容許咱們設置對象的setter和getter的上下this

var name = ... // get property name as a string
Reflect.get(obj, name, wrapper) // if obj[name] is an accessor, it gets run with `this === wrapper`
Reflect.set(obj, name, value, wrapper)

  訪問器中不想使用本身的方法,而是想要重定向this到wrapper:

var obj = {
    set foo(value) { return this.bar(); },
    bar: function() {
        alert(1);
    }
};
var wrapper = {
    bar : function() {
        console.log("wrapper");
    }
}
Reflect.set(obj, "foo", "value", wrapper);

  6:避免直接訪問 __proto__ : ES5提供了 Object.getPrototypeOf(obj),去訪問對象的原型, ES6提供也提供了Reflect.getPrototypeOf(obj) 和  Reflect.setPrototypeOf(obj, newProto), 這個是新的方法去訪問設置對象的原型: 

 

  Reflect.apply的使用

  Reflect.apply其實就是ES5中的 Function.prototype.apply() 替身, 執行Reflect.apply須要三個參數
  第一個參數爲: 須要執行的函數;
  第二個參數爲: 須要執行函數的上下文this;
  第三個參數爲: 是一個數組或者僞數組, 會做爲執行函數的參數;

<script>
let fn = function() {
    this.attr = [0,1,2,3];
};
let obj = {};
Reflect.apply(fn, obj, [])
console.log(obj);  
</script>

  

  Reflect.apply的DEMO:

<script>
Reflect.apply(Math.floor, undefined, [1.75]); // 輸出:1;
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]); // 輸出:"hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index; //輸出: 4
Reflect.apply("".charAt, "ponies", [3]); // 輸出:"i"
</script>

  Reflect能夠與Proxy聯合使用:

{
    var  Fn = function(){
    };
    Fn.prototype.run = function() {
        console.log( "runs out" );
    };
    var ProxyFn  = new Proxy(Fn, {
        construct (target ,arugments) {
            console.log("proxy constructor");
            var obj = new target(...arugments);
            //Reflect.apply的使用方法;
            Reflect.apply(target.prototype.run, obj, arugments);
            return obj;
        }
    });
    new ProxyFn ();  //會先輸出: "proxy constructor" ; 再輸出: runs out
}

  Reflect.construct()的使用:

  Reflect.construct其實就是實例化構造函數,經過傳參形式的實現, 執行的方式不一樣, 效果其實同樣, construct的第一個參數爲構造函數, 第二個參數由參數組成的數組或者僞數組, 基本的使用方法爲:

var Fn = function(arg) {
    this.args = [arg]
};
console.log( new Fn(1), Reflect.construct(Fn,[1]) ); // 輸出是同樣的

var d = Reflect.construct(Date, [1776, 6, 4]);
d instanceof Date; // true
d.getFullYear(); // 1776
//因此Reflect.consturct和new 構

  因此Reflect.consturct和new 構造函數是同樣, 至少到目前爲止..

  咱們能夠給Reflect.construct傳第三個參數 , 第三個參數爲一個超類, 新元素會繼承這個超類;

<script>
function someConstructor() {}
var result = Reflect.construct(Array, [], someConstructor);
Reflect.getPrototypeOf(result); // someConstructor.prototype
Array.isArray(result); // true
//or
var Fn = function() {
    this.attr = [1];
};
var Person = function() {
};
Person.prototype.run = function() {
};
console.log( Reflect.construct(Fn, [], Person) );
</script>

  

  因此咱們能夠用這個實現一個特殊的的數組, 繼承數組, 可是也有本身的方法;

var Fn = function() {
    Array.apply(this, arguments);
    this.shot = ()=> {
        console.log("heheda");
    };
};
var arr = Reflect.construct(Fn, [])

 

  Reflect.defineProperty的使用;

   Reflect.defineProperty返回的是一個布爾值, 經過直接賦值的方式把屬性屬性值添加給對象返回的是一整個對象, 若是添加失敗會拋錯

var obj = {};
obj.x = 10;
console.log(obj.x) //輸出:10;

  使用Reflect.defineProperty的方式添加值;

<script>
var obj = {};
if( Reflect.defineProperty(obj, "x", {value : 7 }) ) {
    console.log("added success");
}else{
    console.log("添加失敗");
};
</script>

  若是咱們執行preventExtensions, 經過Object.defindProperty定義新屬性報錯了, 可是經過Reflect.defineProperty沒有報錯, 返回了一個false的值:

var obj = {};
Object.preventExtensions(obj);
Object.defineProperty(obj, "x" , {
    value: 101,
    writable: false,
    enumerable: false,
    configurable: false
});// 直接拋錯了;
console.log( Reflect.defineProperty(obj, "x", {value:101}) ) //返回false:

  若是經過直接賦值的方式, 不管是否正確賦值, 都返回設置的值, 除非咱們手動確認對象的屬性值是否設置成功;

<script>
var obj = {};
Object.preventExtensions(obj);
console.log( obj.aa = 1 ); //輸出:1;
console.log(obj.aa) //輸出:undefined;
</script>

  Reflect.deleteProperty的使用:

  Reflect.deleteProperty和Reflect.defineProperty的使用方法差很少, Reflect.deleteProperty和 delete obj.xx的操做結果是同樣, 區別是使用形式不一樣:一個是操做符,一個是函數調用;

Reflect.deleteProperty(Object.freeze({foo: 1}), "foo"); // false
delete Object.freeze({foo: 1}).foo; //輸出:false;

  Reflect.get()方法的使用;

  這個方法的有兩個必須的參數: 第一個爲obj目標對象, 第二個爲屬性名對象, 第三個是可選的,是做爲讀取器的上下文(this);

var obj = {};
obj.foo = 1;
console.log( obj.foo ); //輸出:1;
console.log( Reflect.get(obj, "foo") ) //輸出:1;

  若是Reflect.get有第三個參數的話, 第三個參數會做爲讀取器的上下文:

var Reflect = require('harmony-reflect');

var obj = {
    "foo" : 1,
    get bar() {
        return this.foo;
    }
};
var foo = {};
foo.foo = "heheda";
console.log(Reflect.get(obj, "bar", foo));

  

  Reflect.getOwnPropertyDescritptor()方法的使用:

  經過Reflect.getOwnPropertyDescritptor獲取屬性描述

Reflect.getOwnPropertyDescriptor({x: "hello"}, "x");
//也能夠這樣獲取:
Object.getOwnPropertyDescriptor({x:"1"},"x");
//這兩個的區別是一個會包裝對象, 一個不會:
Reflect.getOwnPropertyDescriptor("hello",0); //拋出異常
Object.getOwnPropertyDescriptor("hello",0); //輸出: {value: "h", writable: false, enumerable: true, configurable: false}

  Reflect.getPrototypeOf()方法的使用:

  Reflect.getPrototypeOf和Object.getPrototypeOf是同樣的, 他們都是返回一個對象的原型

Reflect.getPrototypeOf({}); // 輸出:Object.prototype
Reflect.getPrototypeOf(Object.prototype); // 輸出:null
Reflect.getPrototypeOf(Object.create(null)); // 輸出: null

  Reflect.has的使用

  Reflect.has這個方法有點像操做符:in , 好比這樣: xx in obj;

<script>
Reflect.has({x:0}, "x") //輸出: true;
Reflect.has({y:0}, "y") //輸出:true
 var obj = {x:0}; console.log( "x" in obj); var proxy = new Proxy(obj, { has : function(target, args) { console.log("執行has方法"); return Reflect.has(target,...args); } }); console.log( "x" in proxy); //輸出:true; console.log(Reflect.has(proxy, "x")) //輸出:true; </script>

  這個demo的obj至關於變成了一個方法了, 沒他什麼用 , 只是利用了他的has方法:

obj = new Proxy({}, {
    has(t, k) { return k.startsWith("door"); }
});
Reflect.has(obj, "doorbell"); // true
Reflect.has(obj, "dormitory"); // false

  Reflect.isExtensible()的使用

// 如今這個元素是能夠擴展的;
var empty = {};
Reflect.isExtensible(empty); // === true

// 使用preventExtensions方法, 讓這個對象沒法擴展新屬性;
Reflect.preventExtensions(empty);
Reflect.isExtensible(empty); // === false

// 這個對象沒法擴展新屬性, 可寫的屬性依然能夠改動
var sealed = Object.seal({});
Reflect.isExtensible(sealed); // === false

// 這個對象徹底被凍結了
var frozen = Object.freeze({});
Reflect.isExtensible(frozen); // === false

  Reflect.isExtensible和Object.isExtensible的區別是, 若是參數不對,一個會拋錯, 另外一個只是返回true或者false:

Reflect.isExtensible(1);
// 拋錯了: 1 is not an object
Object.isExtensible(1);
// 返回false;

  Reflect.ownKeys()方法的使用:

  Reflect.ownKeys, Object可沒有ownKeys方法, Reflect.ownKeysz他的做用是返回對象的keys;

console.log(Reflect.ownKeys({"a":0,"b":1,"c":2,"d":3})); //輸出 :["a", "b", "c", "d"]
console.log(Reflect.ownKeys([])); // ["length"]
var sym = Symbol.for("comet");
var sym2 = Symbol.for("meteor");
var obj = {[sym]: 0, "str": 0, "773": 0, "0": 0,
    [sym2]: 0, "-1": 0, "8": 0, "second str": 0};
Reflect.ownKeys(obj); //輸出:/ [ "0", "8", "773", "str", "-1", "second str", Symbol(comet), Symbol(meteor) ]

  Reflect.ownKeys的排序是根據: 先顯示數字, 數字根據大小排序,而後是 字符串根據插入的順序排序, 最後是symbol類型的key也根據插入插入順序排序;
  出現這中排序是由於,你給一個對象屬性賦值時候, 對象的key的排序規則就是先數字, 在字符串, 最後是symbol類型的數據;

  Reflect.preventExtensions()的使用方法:

  Object也有preventExtensions方法, 和Reflect.preventExtensions()有一點區別, 若是Reflect.preventExtensions參數不是對象會拋錯;

var empty = {};
Reflect.isExtensible(empty); // === true

// 執行preventExtensions後的對象能夠修改;
Reflect.preventExtensions(empty);
Reflect.isExtensible(empty); // === false

Reflect.preventExtensions(1);
// TypeError: 1 is not an object
Object.preventExtensions(1);
//不會拋錯, 會返回:1

  Reflect.set()

  Reflect.set方法和get是差很少的;

var obj = {};
Reflect.set(obj, "prop", "value"); // 輸出:true
console.log( obj.prop ); // 輸出:"value"

var arr = ["duck", "duck", "duck"];
Reflect.set(arr, 2, "goose"); // true
console.log( arr[2] ); // "goose"

Reflect.set(arr, "length", 1); // true
console.log( arr );// ["duck"];

    Reflect.set(obj)至關於 Reflect.set(obj, undefined, undefined);

var obj = {};
Reflect.set(obj); // 輸出:true
//以上的代碼至關於 Reflect.set(obj, undefined, undefined);
Reflect.getOwnPropertyDescriptor(obj, "undefined");
// { value: undefined, writable: true, enumerable: true, configurable: true }

  Reflect.set也能夠有第四個參數, 這個參數會做爲stter的this;

var obj = {
    value : 10,
    set key( value ) {
        console.log("setter");
        this.value =  value;
    },
    get key() {
        return this.value;
    }
};
Reflect.set(obj,"key","heheda", obj);
console.log(obj);

  Reflect.setPrototypeOf()

  Reflect.setPrototypeOf()方法和Object.setPrototypeOf差很少同樣樣的, 會給對象設置原型, 就是更改對象的__proto__屬性了...;

Reflect.setPrototypeOf({}, Object.prototype); // 輸出true

// 給該對象數組[[Prototype]] 爲null.
Reflect.setPrototypeOf({}, null); // true
// 此時的obj.__proto__爲undefine

//把對象凍結之後從新設置[[prototype]]
Reflect.setPrototypeOf(Object.freeze({}), null); // false

// 若是原型鏈循環依賴的話就會返回false.
var target = {};
var proto = Object.create(target);
Reflect.setPrototypeOf(target, proto); // false

 

  參考:


  MDN:mdn: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
  讓node環境支持Proxy和Reflect:https://github.com/tvcutsem/harmony-reflect/ 

做者: NONO
出處:http://www.cnblogs.com/diligenceday/
QQ:287101329
微信:18101055830 

相關文章
相關標籤/搜索