In Javascript, reading, assigning, calling methods, and so on, almost everything is done around “objects”; For a long time, how to better understand and control these operations has become an important issue in the development of the language.

I. JS object access control

[1.1] Familiar getter/setter

Getters/setters are generally defined as:

  • A getter method takes no arguments and always returns a value
  • A setter always accepts an argument and does not return a value

Some getter/setter common sense:

  • Also known as access methods, they are the two most commonly used access methods
  • Used to encapsulate private member methods to isolate direct access to them
  • Additional logic can also be added during access, ensuring simplicity of external calls
  • Flexibility within the logic of an object or class is implemented, leaving open the possibility of change
  • It can be generated automatically in many ides

Let’s first look at the general implementation in other languages:

One is the traditional explicit getXXX()/setXXX(v) method call

//JAVA
public class People {
    private Integer _age;

    public Integer getAge(a) {
        return this._age;
    }
    public void setAge(Integer age) {
        this._age = age;
    }

    public static void main(String[] args) {

        People p = new People();
        p.setAge(18);
        System.out.println(p.getAge().toString()); / / 18}}Copy the code

There is no doubt that explicit call naming is arbitrary and can be implemented in all languages

The other is implicit getters/setters

//AS2
class Login2 {
    private var _username:String;

    public function get userName(a):String {
        return this._username;
    }
    public function set userName(value:String):Void {
        this._username = value; }}var lg = new Login2;
lg.userName = "tom";
trace(lg.userName); //"tom"Copy the code
//C#
class People
{  
    private string _name;  

    public string name
    {  
        get {
            return _name;
        }  
        set {
            _name = value;
        }  
    }  
} 

People p = new People();
p.name = "tom";
Console.WriteLine(p.name)Copy the code
//PHP
class MyClass {
  private $firstField;
  private $secondField;

  public function __get($property) {
    if (property_exists($this, $property)) {
      return $this->$property; }}public function __set($property, $value) {
    if (property_exists($this, $property)) {
      $this->$property = $value." world";
    }
  }
}

$mc = new MyClass;
$mc->firstField = "hello";
echo $mc->firstField; //"hello world"Copy the code

Implicit access methods require language-specific support and feel like reading properties (var x = obj.x) or assigning values to properties (obj.x = “foo”).

[1.2] Getters and setters in ES5

Starting with the ECMAScript 5.1 (ECMA-262) specification in 2011, JavaScript also began to support getters/setters; In form, of course, it is the same as AS2/AS3, also implemented by ECMAScript

Syntax for getters:

// prop refers to the property name {get to bind to the given functionprop() {... }} // You can also bind a given function with an expression that evaluates the attribute name, note browser compatibility {get [expression]() {... }}Copy the code

🌰 examples:

var obj = {
  log: ['example'.'test'],
  get latest() {
    if (this.log.length == 0) return undefined;
    return this.log[this.log.length - 1];
  }
}
console.log(obj.latest); // "test"

var expr = 'foo';
var obj2 = {
  get [expr]() { return 'bar'; }}; console.log(obj2.foo); //"bar"Copy the code

Note the following when using the GET syntax:

  • You can use numeric values or strings as identifiers
  • Must take no parameters
  • Cannot occur in an object literal with another GET or data entry with the same property

Remove the getter with the delete operator:

delete obj.latest;Copy the code

The following shows an advanced use of the lazy getter, which is evaluated the first time and converted to a normal data property:

get notifier() {
  delete this.notifier;
  return this.notifier = document.getElementById('myId');
},Copy the code

Syntax for setters:

//prop refers to the property name to bind to the given function //val refers to the value assigned to prop {setProp (val) {....}} // You can also bind to a given function using an expression that evaluates the property name, note browser compatibility {set [expression](val) { . . . }}Copy the code

Note the following when using the set syntax:

  • Identifiers can be numbers or strings
  • There must be a clear parameter
  • You cannot use a set for a variable that has a real value in the same object, nor can you set more than one set for an attribute

🌰 examples:

var language = {
  set current(name) {
    this.log.push(name);
  },
  log: []
}
language.current = 'EN'; console.log(language.log); / / /'EN']
language.current = 'FA'; console.log(language.log); / / /'EN'.'FA']

var expr = "foo";
var obj = {
  baz: "bar".set[expr](v) { this.baz = v; }}; console.log(obj.baz); //"bar"
obj.foo = "baz";      // run the setter
console.log(obj.baz); // "baz"Copy the code

Setters can be removed by delete:

delete o.current;Copy the code

[1.4] Define Object members precisely with Object.defineProperty()

As mentioned earlier, there are two main forms of property descriptors that exist in objects: data properties and access methods. The descriptor must be one of two forms, not both.

And in general, attributes added to objects by assigning can be assigned by for… The in or object. keys method iterates through the enumeration; The values of attributes added in this way can be changed or deleted.

var obj = {
    _c: 99,
    get c() {
        returnthis._c; }}; obj.a ='foo';
obj.b = function() {
    alert("hello world!"); }; console.log( Object.keys(obj) ); / / /"_c"."c"."a"."b"]

for (var k in obj) console.log(k); //"_c"."c"."a"."b"

delete obj.b;
delete obj.c;
console.log(obj.b, obj.c); //undefined, undefinedCopy the code

There is no control over whether a data attribute or access method so defined can be deleted or restricted to whether it can be enumerated

Using Object.defineProperty() allows you to change these defaults

Also starting with the ECMAScript 5.1 specification, the object.defineProperty () method is defined. Use to define a new property directly on an object, or modify an existing property of an object, and return the object

The syntax is:

Object. DefineProperty (obj, prop, Descriptor) Object. Object.Copy the code

Descriptor can be set to:

attribute describe Applied to the
configurable Whether it can be modified and deleted Data attributes, access methods
enumerable Whether it can be enumerated Data attributes, access methods
value Attribute values Data attributes
writable Can be changed by the assignment operator Data attributes
get Getter method Access method
set A setter method Access method

It is important to understand that this method has limited support from IE8 onwards (non-DOM objects are not available)

🌰 examples:

var o = {}; o.a = 1; // Equivalent to: object.defineProperty (o,"a", {
  value : 1,
  writable : true,
  configurable : true,
  enumerable : true
});Copy the code
var o = {};

var bValue;
Object.defineProperty(o, "b", {
  get : function(){// Add access methodsreturn bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
  enumerable : true,
  configurable : true
});Copy the code
var o = {};

Object.defineProperty(o, "a", {
    value : 37,
    writable : false// define a "read-only" attribute}); console.log(o.a); // 37 o.a = 25; // In strict mode an error is thrown, but in non-strict mode console.log(o.a) does not work; / / 37Copy the code
var o = {};
Object.defineProperty(o, "a", {
    get : function() {return1; }, configurable :false}); // throws a TypeError Object.defineProperty(o,"a", {configurable : true}); 
// throws a TypeError
Object.defineProperty(o, "a", {enumerable : true}); 
// throws a TypeError
Object.defineProperty(o, "a", {set : function() {}}); // throws a TypeError Object.defineProperty(o,"a", {get : function() {return1; }}); // throws a TypeError Object.defineProperty(o,"a", {value : 12}); console.log(o.a); //1 delete o.a; // In strict mode TypeError is raised, but in non-strict mode console.log(o.a) does not work; / / 1Copy the code
Object.defineProperty(o, "conflict", {
  value: 0x9f91102, 
  get: function() { 
    return0xdeadbeef; }}); // Raises TypeError. Data attributes and access methods cannot be mixedCopy the code

Relevant methods: Object. GetOwnPropertyDescriptor ()

Returns the property descriptor corresponding to a property of its own on the specified object. (Own properties refer to properties that are directly assigned to the object, not properties that are looked up from the prototype chain.)

Grammar:

/ / which prop corresponds to the Object. DefineProperty () in the third parameter descriptor Object. The getOwnPropertyDescriptor (obj, prop)Copy the code

🌰 examples:

var o = {
    get foo() {
        return17. }}; Object.getOwnPropertyDescriptor(o,"foo");
// {
//   configurable: true,
//   enumerable: true,
//   get: /*the getter function* /, / /set: undefined
// }Copy the code

Object.defineproperties ()

Define multiple new properties or modify existing properties directly on an object

Grammar:

// Descriptor definitions correspond to Object.defineProperty() object.defineProperties (obj, {prop1: descriptor1, prop2: descriptor1; descriptor2, ... })Copy the code

🌰 examples:

var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true.writable: true
  },
  'property2': {
    value: 'Hello'.writable: false}});Copy the code

Object.create()

Creates a new object using the specified prototype object and its properties

Grammar:

//proto is the prototype Object for the newly created Object //props corresponds to the second parameter object.defineProperties () object.create (proto[, props])Copy the code

🌰 examples:

// Create an empty object whose prototype is null
var o = Object.create(null);

var o2 = {};
// An empty object created literally is equivalent to:
var o2 = Object.create(Object.prototype);Copy the code
var foo = {a:1, b:2}; Var o = object.create (foo, {// foo will become the data attribute of the created Object foo: {writable:true,
    configurable:true,
    value: "hello"}, // the bar becomes the arbitrary accessor property of the created object.false,
    get: function() { return10},set: function(value) {
      console.log("Setting `o.bar` to", value); }}});Copy the code

[1.5] __define[G,S]etter__()

As non-standard and deprecated methods, defineGetter() and defineSetter() sometimes show up in historical code and still run in browsers like Firefox/Safari/Chrome

🌰 look directly at examples:

var o = {
    word: null
};

o.__defineGetter__('gimmeFive'.function() { 
    return 5;
});
console.log(o.gimmeFive); // 5

o.__defineSetter__('say'.function(vlu) { 
    this.word = vlu;
});
o.say = "hello";
console.log(o.word); //"hello"Copy the code

[1.6] __lookup[G,S]etter__()

There are also two non-standard and obsolete lookupGetter() and lookupSetter() methods

  • LookupGetter () returns the getter function for a property on the object

🌰 examples:

var obj = {
    get foo() {
        returnMath. The random () > 0.5?"foo" : "bar"; }}; obj.__lookupGetter__("foo") / / (function() {returnMath. The random () > 0.5?"foo" : "bar"})Copy the code

If the standard method is used, it is:

Object.getOwnPropertyDescriptor(obj, "foo").get
// (function() {returnMath. The random () > 0.5?"foo" : "bar"})Copy the code

And if that accessor property is inherited:

Object.getOwnPropertyDescriptor(Object.getPrototypeOf(obj), "foo").get 
// function __proto__() {[native code]}Copy the code
  • LookupSetter () returns a setter function for a property of the object

🌰 examples:

var obj = {
  setfoo(value) { this.bar = value; }}; obj.__lookupSetter__('foo') / / (function(value) { this.bar = value; }) // Standard and recommended usage. Object.getOwnPropertyDescriptor(obj,'foo').set; / / (function(value) { this.bar = value; })Copy the code

[1.7] Compatible with old browsers with onPropertyChange

In extreme cases requiring compatibility with IE6/IE7 browsers, the onPropertyChange event supported by IE can also simulate getters/setters

Note that this method is limited to DOM objects that have been loaded into the document

function addProperty(obj, name, onGet, onSet) {
    var
        oldValue = obj[name],
        getter = function () {
            return onGet.apply(obj, [oldValue]);
        },
        setter = function (newValue) {
            return oldValue = onSet.apply(obj, [newValue]);
        },
        onPropertyChange = function (event) {
            if(event.propertyName == name) {// Temporarily remove event listener to avoid loop call obj.detachEvent("onpropertychange", onPropertyChange); Var newValue = setter(obj[name]); // Set getter obj[name] = getter; obj[name].toString = getter; // Restore event listener obj.attachEvent("onpropertychange", onPropertyChange);
            }
        };
    // 设置 getter
    obj[name] = getter;
    obj[name].toString = getter;

    obj.attachEvent("onpropertychange", onPropertyChange);
}Copy the code

Proxy and reflection in ii.js

In the object itself, the access control of one attribute after another will sometimes bring bloated code, and even difficult to maintain; Understanding the concepts and uses of proxy and reflection can improve these situations.

[2.1] Traditional proxy mode

In the classical Design Pattern, Proxy Pattern is widely used. It is defined as:

In the Proxy pattern, a Proxy object acts as an interface to another Real Subject object. The proxy object sits between the Client of the target object and the target object itself, and is responsible for protecting access to the target object.

Typical application scenarios are as follows:

  • Access control and caching of target objects
  • Delays the initialization of the target object
  • Accessing remote objects

🌰 for example:

function Book(id, name) {
    this.id = id;
    this.name = name;
}

function BookShop() {
    this.books = {};
}
BookShop.prototype = {
    addBook: function(book) {
        this.books[book.id] = book;
    },
    findBook: function(id) {
        return this.books[id]; }}function BookShopProxy() {
}
BookShopProxy.prototype = {
    _init: function() {
        if (this.bookshop)
            return;
        else
            this.bookshop = new BookShop;
    },
    addBook: function(book) {
        this._init();
        if (book.id in this.bookshop.books) {
            console.log('existed book! ', book.id);
            return;
        } else {
            this.bookshop.addBook(book); }},findBook: function(id) {
        this._init();
        if (id in this.bookshop.books)
            return this.bookshop.findBook(id);
        else 
            return null; }}var proxy = new BookShopProxy;
proxy.addBook({id:1.name:"head first design pattern"});
proxy.addBook({id:2.name:"thinking in java"});
proxy.addBook({id:3.name:"lua programming"});
proxy.addBook({id:2.name:"thinking in java"}); //existed book! 2

console.log(proxy.findBook(1)); //{ id: 1, name: 'head first design pattern' }
console.log(proxy.findBook(3)); //{ id: 3, name: 'lua programming' }Copy the code

Obviously, the use of proxies for lazy initialization and access control is shown in the sample code above.

It is important to note that the proxy Pattern is easily confused with the Decorator Pattern, another of the design patterns. Both are similar in that they wrap the original target object. The difference is that the former focuses on providing the same API as the original object and protecting its access control, while the latter focuses on adding new functionality on top of the original API.

[2.2] Proxy in ES6

In the ECMAScript 2015 (6th Edition, ECMA-262) standard, a native Proxy object is proposed. Custom behavior for defining basic operations (such as property lookup, assignment, enumeration, function call, etc.)

Grammar:

let p = new Proxy(target, handler);Copy the code

The target Object of a proxy Object can be any type of Object, such as Object, Array, Function, or even another proxy Object. After the let proxy=new proxy (target,handle) operation is performed, proxy and target affect each other. That is:

let target = {
    _prop: 'foo'.prop: 'foo'
};
let proxy = new Proxy(target, handler);
proxy._prop = 'bar';
target._attr = 'new'
console.log(target._prop) //'bar'
console.log(proxy._attr) //'new'Copy the code

Handler is also an object whose specified properties define functions that represent the operations to be performed when the corresponding access to the target object is performed. The most common operations are to define get and set properties for getters/setters:

let handler = {
    get (target, key){
        return key in target
            ? target[key]
            : - 1; / / the default value
    },
    set (target, key, value) {
        if (key === 'age') { / / check
            target[key] = value > 0 && value < 100 ? value : 0
        }
        return true; }};let target = {};
let proxy = new Proxy(target, handler);
proxy.age = 22 / / 22Copy the code

Note that unlike the setters for the object itself in ES5, setters in the proxy must return values;

It should also be easy to understand that Proxy objects conform to the classic Proxy pattern — Proxy objects encapsulate and protect the TARGET object’s API, hide the target object, and control access to it.

In addition to defining getters/setters, the more complete handler attributes are as follows:

  • “get”: function (oTarget, sKey)
  • “set”: function (oTarget, sKey, vValue)
  • “enumerate”: function (oTarget, sKey)
  • “ownKeys”: function (oTarget, sKey)
  • “has”: function (oTarget, sKey)
  • “defineProperty”: function (oTarget, sKey, oDesc)
  • “deleteProperty”: function (oTarget, sKey)
  • “getOwnPropertyDescriptor”: function (oTarget, sKey)
  • “getPrototypeOf(oTarget)”
  • “setPrototypeOf(oTarget, oPrototype)”
  • “apply(oTarget, thisArg, argumentsList)”:
  • “construct(oTarget, argumentsList, newTarget)”
  • “isExtensible(oTarget)”
  • “preventExtensions(oTarget)”

[2.3]

Reflection on objects is a language capability that probes and manipulates object properties at runtime.

In languages such as JAVA/AS3, reflection is typically used to get the class name of an object at runtime, a list of attributes, and then construct it dynamically. Such as creating objects dynamically from values in XML configuration files, or extracting MovieClip from SWF files by name, etc.

JS originally also has related the reflection API, such as the Object. The getOwnPropertyDescriptor () Function. The prototype, the apply (), in, delete, etc., but these apis distribution in different namespace, and even global reserved words, And the execution fails by throwing an exception. These factors make code involving object reflection difficult to write and maintain.

[2.4] Reflect in ES6

Along with Proxy, in ECMAScript 2015 (6th Edition, ECMA-262), the Reflect object was introduced to include several methods for object reflection.

The reflection method Similar operations
Reflect.apply() Function.prototype.apply()
Reflect.construct() new target(… args)
Reflect.defineProperty() Object.defineProperty()
Reflect.deleteProperty() delete target[name]
Reflect.enumerate() For the for… Attribute traversed by the in operation
Reflect.get() Similar to the target, [name]
Reflect.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor()
Reflect.getPrototypeOf() Object.getPrototypeOf()
Reflect.has() The in operator.
Reflect.isExtensible() Object.isExtensible()
Reflect.ownKeys() Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
Reflect.preventExtensions() Object.preventExtensions()
Reflect.set() target[name] = val
Reflect.setPrototypeOf() Object.setPrototypeOf()
  • Reflect is a bit like ES5 Object in that it contains methods from within the Object language, and reflects also has several methods that correspond to proxies one by one.
  • Proxy is used to modify the behavior of setting an object’s properties, and Reflect is used to get the behavior of the object. The two are often used together.
  • Reflect has no constructors; all that can be called are its static methods.
var target = {
    a: 1
};
var proxy = new Proxy(target, {
    get: function(tgt, key) {
        console.log("Get %s", key);
        return tgt[key] + 100;
    },
    set: function(tgt, key, val) {
        console.log("Set %s = %s", key, val);
        return tgt[key] = "VAL_"+ val; }}); proxy.a =2;
//Set a = 2

console.log(proxy.a);
//Get a
//VAL_2100

console.log(Reflect.get(target, "a"));
//VAL_2

Reflect.set(target, "a".3);
console.log(Reflect.get(target, "a"));
/ / 3Copy the code

As you can see, if you access the value of the target object directly from the Proxy, you are likely to call extra getters/setters; The corresponding method in Reflect can effectively avoid this situation

Note also that these methods do not throw errors but return false on failure; This greatly simplifies processing:

//In ES5
var o = {};
Object.defineProperty(o, 'a', {
  get: function() { return 1; },
  configurable: false
});
try {
    Object.defineProperty(o, 'a', { configurable: true });
} catch(e) {
    console.log("Exception");
}

//In ES2015
var o = {};
Reflect.defineProperty(o, 'a', {
  get: function() { return 1; },
  configurable: false
});
if(!Reflect.defineProperty(o, 'a', { configurable: true{}))console.log("Operation Failed");
}Copy the code

[2.5] Use Proxy/Reflect

🌰 Example 1: Set the getter/setter for each property of the object

//in ES5
var obj = {
    x: 1.y: 2.z: 3
};

function trace1() {
    var cache = {};
    Object.keys(obj).forEach(function(key) {
        cache[key] = obj[key]; // Avoid looping setters
        Object.defineProperty(obj, key, {
            get: function() {
                console.log('Get ', key);
                return cache[key];
            },
            set: function(vlu) {
                console.log('Set ', key, vlu); cache[key] = vlu; }})}); } trace1(); obj.x =5;
console.log(obj.z);
// Set x 5
// Get z
/ / 3Copy the code
//in ES6
var obj2 = {
    x: 6.y: 7.z: 8
};

function trace2() {
    return new Proxy(obj2, {
        get(target, key) {
            if (Reflect.has(target, key)) {
                console.log('Get ', key);
            }
            return Reflect.get(target, key);
        },
        set(target, key, vlu) {
            if (Reflect.has(target, key)) {
                console.log('Set ', key, vlu);
            }
            return Reflect.set(target, key, vlu); }}); }const proxy2 = trace2();
proxy2.x = 99;
console.log(proxy2.z);
// Set x 99
// Get z
/ / 8Copy the code

🌰 Example 2: Tracing method calls

var obj = {
    x: 1.y: 2.say: function(word) {
        console.log("hello ", word)
    }
};

var proxy = new Proxy(obj, {
    get(target, key) {
        const targetValue = Reflect.get(target, key);
        if (typeof targetValue === 'function') {
            return function (. args) {
                console.log('CALL', key, args);
                return targetValue.apply(this, args); }}else {
            console.log('Get ', key);
            returntargetValue; }}}); proxy.x; proxy.y; proxy.say('excel! ');
// Get x
// Get y
// CALL say [ 'excel!' ]
// hello excel!Copy the code

conclusion

  • Getters/setters, also known as access methods, are the two most common access methods
  • Access methods can be encapsulated to protect the original object while preserving logical flexibility
  • Implicit get and set access methods are now supported in ES5, which can be removed by DELETE
  • You can also set getters/setters using Object.defineProperty()
  • Historically, Object.prototype.define[G,S]etter() and onPropertyChange were used to implement compatibility of access methods
  • Traditional access control can be improved with proxies and reflection
  • The proxy object sits between the user of the target object and the target object itself and is responsible for securing access to the target object
  • ES6 native Proxy object. Custom behavior for defining basic operations (such as property lookup, assignment, enumeration, function call, etc.)
  • Object reflection is a language capability that probes and manipulates object properties at run time
  • ES6 introduces the Reflect object, which encapsulates several methods of object reflection
  • Reflect has a one-to-one counterpart to Proxy and is often used together

References:

  • Dealwithjs. IO/es6 – feature…
  • Segmentfault.com/a/119000000…
  • 2 ality.com/2017/11/pro…
  • www.zhihu.com/question/21…
  • Blogs.msdn.microsoft.com/ie/2010/09/…
  • Johndyer. Name/native – brow…
  • Developer.mozilla.org/zh-CN/docs/…
  • www.cnblogs.com/onlywujun/a…
  • www.joezimjs.com/javascript/…
  • Stackoverflow.com/questions/7…
  • www.importnew.com/9716.html
  • Help.adobe.com/en_US/AS2LC…
  • Blog.csdn.net/chy555chy/a…
  • www.runoob.com/csharp/csha…
  • Php.net/manual/en/l…
  • Antimatter15.com/wp/2010/02/…
  • www.52jbj.com/jbdq/512047…
  • Ponyfoo.com/articles/es…
  • www.keithcirkel.co.uk/metaprogram…
  • 2 ality.com/2014/12/es6…
  • Qnimate.com/es6-reflect…


(end)