JavaScript

物件屬性存取

物件屬性存取

const person = {};

// set
person.name = 'Bob';

// get
const name = person.name;

物件屬性存取

0. getter & setter

1.  Object.defineProperty

2. Proxy & Reflect

 

3. Private Property

getter 跟 setter

let name;

const person = {
  get name() {
    return name;
  },
  set name(newName) {
    name = newName;
  },
}

person.name = 'Bob';
console.log(person.name); // Bob

ES5.1

getter 跟 setter

let name;

function getName() {
  return name;
}

function setName(newName) {
  name = newName;
}

const person = {}

person.__defineGetter__('name', getName);
person.__defineSetter__('name', setName);

person.name = 'Bob';
console.log(person.name); // Bob

ES5 以前

getter 跟 setter

屬性值 取出 跟 存入 的時候叫的 function

function doNothing() {}

const person = {
  get name() {
    return 'Stanney';
  },
  set name(newName) {
    doNothing();
  },
};

person.name = 'Bob';
console.log(person.name); // Stanney

Object.defineProperty

parameters:

    (1) targetObject

    (2) key (property name)

    (3) descriptor

            .get

            .set

            .value

            .writable

            .enumerable

            .configurable

 

ES5.1

function doNothing() {}

const person = {};

Object.defineProperty(person, 'name', {
  get() {
    return 'Stanney';
  },
  set(newName) {
    doNothing();
  },
});

Object.defineProperty

parameters:

    (1) targetObject

    (2) key (property name)

    (3) descriptor

            .get

            .set

            .value

            .writable

            .enumerable

            .configurable

 

ES5

const person = { name: 'Bob' };

console.log(Object.getOwnPropertyDescriptor(person, 'name'));

// {
//   value: "Bob",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

-> 能不能修改屬性值

-> 能不能用 Object.keys(target), for ... in

-> 能不能改屬性的 descriptor

const person = {};
Object.defineProperty(person, 'name', { value: 'Bob' });

console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// {
//   value: "Bob",
//   writable: false,
//   enumerable: false,
//   configurable: false
// }

Object.defineProperty

getter, setter 無法被綁定到

同時擁有實際數值的屬性

const personA = { get name() { return 'Bob' } };

Object.getOwnPropertyDescriptor(personA, 'name');
// { 
//   get: ƒ,
//   set: undefined,
//   enumerable: true,
//   configurable: true
// }

const personB = { name: 'Bob' };

Object.getOwnPropertyDescriptor(personB, 'name');
// {
//   value: "Bob",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

Proxy

const person = {};

const handler = {
  get(target, prop) {
    if (prop === 'name') return 'Bob';

    return target[prop];
  },
};

const personYouSee = new Proxy(person, handler);

console.log(personYouSee.name); // Bob

ES6

parameters:

    (1) targetObject

    (2) handler

 

Proxy

handler

.has()

.get()

.set()

.getPrototypeOf()

.setPrototypeOf()

.isExtensible()

.preventExtensions()

.getOwnPropertyDescriptor()

.defineProperty()

.deleteProperty()

.ownKeys()

.apply()

.construct()

the in operator

getting property values

setting property values

Object.getPrototypeOf

Object.setPrototypeOf

Object.isExtensible

Object.preventExtensions

Object.getOwnPropertyDescriptor

Object.defineProperty

the delete operator

Object.getOwnPropertyNames (Symbols)

function call

the new operator

Reflect

.has()

.get()

.set()

.getPrototypeOf()

.setPrototypeOf()

.isExtensible()

.preventExtensions()

.getOwnPropertyDescriptor()

.defineProperty()

.deleteProperty()

.ownKeys()

.apply()

.construct()

prop in target

target[prop]

target[prop] = newValue

Object.getPrototypeOf

Object.setPrototypeOf

Object.isExtensible

Object.preventExtensions

Object.getOwnPropertyDescriptor

Object.defineProperty

delete target[prop]

Object.getOwnPropertyNames (Symbols)

target[prop].apply()

new target()

和 proxy 可以代理的行為一對一對應

Reflect.has(person, 'name') =

'name' in person 

Proxy & Reflect

const personYouSee = new Proxy(person, {
  get(target, prop) {
    if (prop === 'name') return 'Bob';

    return Reflect.get(target, prop);
  }
};

Proxy

Object.defineProperty

定義行為

定義屬性

Private Property

變數名稱前面加底線


const person = {
    _name = 'Bob',
};

console.log(person._name); // Bob

假的私有變數

constructor 內定義 closure

function funcA() {
  const scoped = 'secret';
  
  function funcB() {
    console.log(scoped);
  }

  return funcB;
}

const printSecret = funcA();
printSecret(); // secret

什麼是 closure

閉包(Closure)是函式以及該函式被宣告時所在的作用域環境(lexical environment)的組合。(MDN)

Operationally, a closure is a record storing a function[a] together with an environment. (Wikipedia)

constructor 內定義 closure


function Person() {
  const name = 'Bob';
  
  this.greet = function() {
    console.log(`Hi, I'm ${name}.`);
  };
}

const person = new Person();
person.greet(); // Hi, I'm Bob.

有用到私有變數的方法,不能定義在原型上

scoped WeakMap

什麼是 WeakMap?

ES6

1. collection of key/value pairs

2. key 一定要是 object,value 隨意

3. keys are weakly referenced

const person = {};
const weakMap = new WeakMap();

weakMap.set(person, 'someValue');

console.log(weakMap.get(person)); // someValue

weakMap.delete(person);
console.log(weakMap.has(person)); // false

scoped WeakMap

let person = {};

const weakMap = new WeakMap();

weakMap.set(person, 'someValue');

console.log(weakMap.get(person)); // someValue

person = null;

// 不需要先 weakMap.delete(person),
// 原來的 person/value pair 之後也會消失,
// 不會造成某種形式的 memory leak

什麼是 WeakMap?

keys are weakly referenced

scoped WeakMap

什麼是 WeakMap?

一個暫時放某個 object 的東西的地方

scoped WeakMap

const Person = (function () {
  const privateProps = new WeakMap();

  class Person {
    constructor() {
      privateProps.set(this, { name: 'Bob' });
    }

    greet() {
      console.log(`Hi, I'm ${privateProps.get(this).name}.`);
    }
  }

  return Person;
})();

const person = new Person();
person.greet(); // Hi, I'm Bob.

立即函式 (Immediately Invoked Function Expression)

scoped WeakMap 利用模組


const privateProps = new WeakMap();

class Person {
  constructor() {
    privateProps.set(this, { name: 'Bob' });
  }

  greet() {
    console.log(`Hi, I'm ${privateProps.get(this).name}`);
  }
}

export default Person;

scoped Symbol

const key = Symbol();

const object = {
  [key]: '1234',
};

console.log(object[key]); // 1234

什麼是 Symbol?

ES6

1. primitive data type

2. every symbol value returned from Symbol() is unique

3. 可以作為 object 的 key

scoped Symbol

const Person = (function () {
  const nameKey = Symbol();

  class Person {
    constructor() {
      this[nameKey] = 'Bob';
    }

    greet() {
      console.log(`Hi, I'm ${this[nameKey]}`);
    }
  }

  return Person;
})();

const person = new Person();
person.greet(); // Bob

scoped Symbol 利用模組

const nameKey = Symbol();

class Person {
  constructor() {
    this[nameKey] = 'Bob';
  }

  greet() {
    console.log(`Hi, I'm ${this[nameKey]}`);
  }
}

export default Person;

const nameKey = Symbol();

class Person {
  constructor() {
    this[nameKey] = 'Bob';
  }

  greet() {
    console.log(`Hi, I'm ${this[nameKey]}`);
  }
}

const person = new Person();

console.log(Object.keys(person));                 // []
console.log(Object.getOwnPropertyNames(person));  // []

const [sameNameKey] = Object.getOwnPropertySymbols(person);

scoped Symbol

Proxy

const instanceYouSee = new Proxy(instance, {
  get: function(target, prop, value) {
    if (prop.startsWith('_')) throw new Error();

    return Reflect.get(target, prop);
  },
  set: function(target, prop, value) {
    if (prop.startsWith('_')) throw new Error();

    Reflect.set(target, prop, value);
  },
  has: function(target, prop) {
    return prop.startsWith('_') ? false : Reflect.has(target, prop);
  },
};

https://blog.techbridge.cc/2018/05/27/js-proxy-reflect/

Proxy

class Person {
  constructor() {
    this._name = 'Bob';

    return new Proxy(this, {
      get: function(target, prop, value) {
        if (prop.startsWith('_')) throw new Error();

        return Reflect.get(target, prop);
      },
      set: function(target, prop, value) {
        if (prop.startsWith('_')) throw new Error();

        Reflect.set(target, prop, value);
      },
      has: function(target, prop) {
        return prop.startsWith('_') ? false : Reflect.has(target, prop);
      },
    });
  }
}

https://blog.techbridge.cc/2018/05/27/js-proxy-reflect/

Thank you

Javascript 物件屬性

By luyunghsien

Javascript 物件屬性

  • 490