JavaScript语言的对象的属性设计及相关

226 阅读5分钟

对象也称为“散列、映射、字典、关联数组",与C、C++、Java等强类型语言不同,JavaScript是松散类型语言,对象不只有固定数量的属性,无需提前定义属性名字。

JavaScript一个设计缺陷:对象的属性名必须是字符串,采用其他数据类型作为key,JavaScript会做一件蠢事——将变量转换为string

后面语言发展中为了弥补这个缺陷出现了symbol、map、weakMap等语法

访问对象属性的语法:点、括号

let a={},b={key:'b'},c={key:'c'};
a[b]=123;
a[c]=456;
console.log(a[b]);// 456
console.log(a);//  {[object Object]: 456}
console.log(b);// {key: "b"}

let a={},b={key:'b'},c={key:'c'};
a.b=123;
a.c=456;
console.log(a.b);// 123
console.log(a);//  {b: 123, c: 456}
console.log(b);// {key: "b"}

注意:点语法or中括号都是在定义object的键,定义object的属性可以采用数值、字符串、符号为键,key会被隐式转换为string,[]语法会先计算再转换。

  • 但是map可以采用JavaScript所有类型为键,map内部采用sameValueZero比较(类似===)来匹配键
let a={},b='b1';
a[b]=123;
console.log(a); // {b1: 123}
b='b2'
console.log(a); // {b1: 123}
const m =new Map();
m.set(b,123);
console.log(m); // {b2 => 123}
b='b3'
console.log(m); // {b2 => 123}
const obj={s:'s1'}
m.set(obj,123);
console.log(m); // Map {"b2" => 123, {s: "s1"} => 123} 
console.log(m); //  Map {"b2" => 123, {s: "s2"} => 123} 

  • 点语法是首选,静态。属性名是通过标识符来表示,标识符必须直接写在JavaScript程序中,不是一种数据类型,因此不能被程序操作
  • 中括号动态,属性名是通过字符串来表示的,是一种JavaScript数据类型,因此可以在程序运行期间修改和创建。提供了更灵活的语法,在程序运行之前不知道属性名的情况

注意:是设置键那一刻静态的string

let addr='';
for(let i=0;i<4;i++){
	addr+=customer[`address${i}`]+"\n";
}

在括号中使用属性名的字符串形式,即必须是一个计算结果为字符串的表达式

利用方括号语法实现去重

原理:对象的属性key不能重复

function noRepeat(arr){
  var i=-1,
    obj={},
    res=[];
  while(++i<arr.length){
    obj[arr[i]]||res.push(arr[i]);
    obj[arr[i]]=true;
  }
  return res;
}

console.log(noRepeat([2,2,4,3,4,3]));
 

下列情况中采用中括号
  1. 通过变量访问属性
  2. 属性名中包含可能会导致语法错误的字符,e.g:空格
  3. 属性名中包含关键字/保留字
  4. 以symbol为键的属性

Symbol作为对象属性

隐藏即特殊的方法访问:symbol、weakMap实现私有变量

注意理解: [symbol]是[计算属性]的真子集

// 另一个代码层级
let user = { 
 id: "John"
};
// 当前代码层级
let id = Symbol("id");
user[id] = 1;
console.log(( user[id] )); // 1,可以使用 Symbol 作为键来访问数据
console.log(( user.id )); // John ,可以使用 Symbol 作为键来访问数据

Symbol值通过Symbol函数生成 Symbol 允许我们创建对象的“隐藏”属性,代码的任何其他部分都不能意外访问或重写这些属性。 如果我们要在对象字面量 {...} 中使用 Symbol,则需要使用方括号把它括起来。

var s=Symbol("hh");
let user = { // 属于另一个代码
 id: "John",
 [s]:"dd"
};
console.log(typeof(( user[s] ))); // string
console.log(typeof(s)); // Symbol
user[s]="ss";
console.log(( user.s )); // undefined
console.log(( user[s] )); // ss
console.log(( user.id )); // John 

BOM使用symbol: 在这里插入图片描述

weakMap 关联对象属性

js垃圾回收程序中对待“弱映射”中键的方式

  • key必须是object或者继承自object,不能为空
  • 保证只有通过键对象的引用才能取得值
  • 一旦指针key发生变化(不被引用),key对应的属性就会被回收

隐藏即特殊的方法访问:symbol、weakMap实现私有变量 weakMap的优越性在于,关联的方式不受对象属性限制,即使对象被冻结也可以实现关联一个秘密属性

var Pclass = (function(){
    const aa = new WeakMap();
    const mt = new WeakMap();
    class  Pclass {
        constructor(){
            this.b = 'b这是公有变量';
            aa.set(this, '私有变量aa')
            mt.set(this, function(){
                console.log('私有方法mt')
            })
        }//实现了constructor
        getA(){
            console.log(aa.get(this));
        }
        getM(){
            console.log(mt.get(this));
        }
    }
    return Pclass
}())
let pc = new Pclass();
console.log(pc) // Pclass {b: "b这是公有变量"}
for (const key in pc){
  console.log(key);//b
}

应用:缓存计算属性

基于一个对象执行某些耗时操作之后得出一个计算值,后续考虑到效率,会缓存这个计算值。 如果用Map建立对象和该值的连接,就会阻止对象的回收,那么采用WeapMap

使用一个私有的Symbol属性也可以实现该效果。

const m =new WeakMap();
let obj={s:'s1'}
m.set(obj,123);
console.log(m); // Map { {s: "s1"} => 123} 
obj.s='s2';
console.log(m); //  Map { {s: "s2"} => 123}
obj={};
console.log(m); //  Map { {s: "s2"} => 123}
obj=null;
console.log(m); //  Map {} 
console.log(m); //  Map {} 

在这里插入图片描述 在这里插入图片描述

应用:关联元数据

WeakMap实例不会阻碍垃圾回收,所以特别适合关联元数据。节点变化之后,垃圾回收程序立刻释放其内存。

const wm=new WeakMap();
const loginButton=document.querySelector('#login');//id选择器
wm.set(loginButton,{disabled:true});//给节点关联一些元数据

应用:真正的私有变量

对象实例为键,私有成员的字典为值

const wm=new WeakMap();

class User{
    construction(id){
        this.idP=Symbol('id');//
        this.setId(id);
    }
    setPrivate(property,value){
        const privateMember=wm.get(this)||{};//查看vm中是否存在User实例对应私有变量
        privateMember[property]=value;
        wm.set(this,privateMember);
    }
    getPrivate(property){
        return wm.get(this)[property];
    }
    setId(id){
        this.setPrivate(this.idP,id);
    }
    getId(){
        return this.getPrivate(this.idP);
    }
}

const user=new User(123);
alert(user.getId());//123
user.setId(456);
alert(user.getId());//456
//并不是真正的私有
alert(wm.get(user)[user.idP]);//456

外部代码只要拿到对象实例的引用和弱映射,就可以取得私有变量。为了避免这种访问可以用一个闭包把weakMap包装起来,这样就可把弱映射与外界完全隔离开。

const User=(()=>{
    const wm=new WeakMap();
    class User{
        construction(id){
            this.idP=Symbol('id');//
            this.setId(id);
        }
        setPrivate(property,value){
            const privateMember=wm.get(this)||{};//查看vm中是否存在User实例对应私有变量
            privateMember[property]=value;
            wm.set(this,privateMember);
        }
        getPrivate(property){
            return wm.get(this)[property];
        }
        setId(id){
            this.setPrivate(this.idP,id);
        }
        getId(){
            return this.getPrivate(this.idP);
        }
    }
})();

const user=new User(123);
alert(user.getId());//123
user.setId(456);
alert(user.getId());//456
//拿不到弱映射中的键,也就无法取得弱映射中对应的值。闭包私有变量

WeakSet 弱集合

给对象打标签,对象被删除自动回收

const disabledE=new WeakSet();
const loginButton=document.querySelector('#login');//id选择器
disabledE.add(loginButton);//通过加入对应集合,打上标签
```# 条件式属性访问的语法
ES2020新增
针对访问undefined、null的属性报错

```javascript
let a={b:null};
a.b?.c.d;//不确定b是否存在
a.b?.[s];

结果是undefined,不报错

补充条件式调用

function square(x,log){
            if(log){
                log(x);
            }
            //替换为
           // log?.(x);
            return x*x;
        }

条件式属性访问的语法

ES2020新增 针对访问undefined、null的属性报错

let a={b:null};
a.b?.c.d;//不确定b是否存在
a.b?.[s];

结果是undefined,不报错

补充条件式调用

function square(x,log){
            if(log){
                log(x);
            }
            //替换为
           // log?.(x);
            return x*x;
        }