JavaScript中的对象(下)

简介: JavaScript中的对象(下)

js中的对象(下)

属性特征

JS 中可以对属性的访问特性进行控制。

查看特征

使用 Object.getOwnPropertyDescriptor查看对象属性的描述。

const obj = {
  name: 'kevin',
  age: 20
};
let desc = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(desc);
// {
//   value: 'kevin',
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

使用 Object.getOwnPropertyDescriptors查看对象所有属性的描述

const obj = {
  name: 'kevin',
  age: 20
};
let desc = Object.getOwnPropertyDescriptors(obj);
console.log(desc);
// {
//   name: {
//     value: 'kevin',
//     writable: true,
//     enumerable: true,
//     configurable: true
//   },
//   age: { value: 20, writable: true, enumerable: true, configurable: true }
// }

属性包括以下四种特性

特性 说明 默认值
configurable 能否使用 delete、能否修改属性特性、或能否修改访问器属性 true
enumerable 对象属性是否可通过 for-in 循环,或 Object.keys() 读取 true
writable 对象属性是否可修改 true
value 对象属性的默认值 undefined

设置特征

使用Object.defineProperty 方法修改属性特性,通过下面的设置属性 name 将不能被遍历、删除、修改。

'use strict';
const obj = {
  name: 'kevin',
  age: 20
};
Object.defineProperty(obj, 'name',{
  value: 'tom',
  writable: false,
  enumerable: false,
  configurable: false
});

// 不允许修改
// obj.name = 'uu'; ////Error

// 不允许遍历
// console.log(Object.keys(obj));//[ 'age' ]

// 不允许删除
// delete obj.name; //Error Cannot delete property 'name' of #<Object>

// 不允许配置
// Object.defineProperty(obj, 'name',{
//   value: 'hh',
//   writable: false,
//   enumerable: false,
//   configurable: true
// }); //Cannot redefine property: name

使用 Object.defineProperties 可以一次设置多个属性,具体参数和上面介绍的一样。

'use strict';
const obj = {
  name: 'kevin',
  age: 20
};
Object.defineProperties(obj, {
  name: {writable: false},
  age: {value: 18}
});
console.log(obj);//{ name: 'kevin', age: 18 }
obj.name = 'tom';
//TypeError: Cannot assign to read only property 'name' of object '#<Object>'

禁止添加

Object.preventExtensions 禁止向对象添加属性

'use strict';
const obj = {
  name: 'kevin',
  age: 20
};
Object.preventExtensions(obj);
//严格模式下添加属性报错
// TypeError: Cannot add property site, object is not extensible
obj.site = 'this is a site';
//非严格模式下
console.log(obj.site);//undefined

Object.isExtensible 判断是否能向对象中添加属性

'use strict';
const obj = {
  name: 'kevin',
  age: 20
};
Object.preventExtensions(obj);
console.log(Object.isExtensible(obj));//false

封闭对象

Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false

不能添加新属性,不能使用 delete、不能修改属性特性、不能修改访问器属性。

'use strict';
const obj = {
  name: 'kevin',
  age: 20
};
Object.seal(obj);
console.log(Object.getOwnPropertyDescriptors(obj));//configurable的值为false
obj.site = 'this is a site';
// TypeError: Cannot add property site, object is not extensible

Object.isSealed 如果对象是密封的则返回 true,属性都具有 configurable: false。

'use strict';
const obj = {
  name: 'kevin',
  age: 20
};
Object.seal(obj);
console.log(Object.isSealed(obj));//true

冻结对象

Object.freeze 冻结对象后不允许添加、删除、修改属性,writable、configurable 都标记为false,只能取值和遍历

'use strict';
const obj = {
  name: 'kevin',
  age: 20
};
Object.freeze(obj);
console.log(obj.name);
console.log(Object.keys(obj));//[ 'name', 'age' ]
console.log(Object.isFrozen(obj));//true

用freeze方法可以很好的快速对对象进行保护。

属性访问器

getter 方法用于获得属性值(getter方法必须有且只有一个形参),setter 方法用于设置属性。这是 JS 提供的存取器特性即使用函数来管理属性。

  • 用于避免错误的赋值
  • 需要动态监测值的改变
  • 属性只能在访问器和普通属性任选其一,不能共同存在

getter/setter

下面是对用户的年龄数据使用访问器监控控制

'use strict';
const obj = {
  date: {name: 'kevin', age: 20},
  set age(value){
    if(typeof value !== 'number' || value>100 || value < 0){
      throw new Error('年龄错误');
    }
    else{
      this.date.age = value;
    }
  },
  get age(){
    return `${this.date.name}今年${this.date.age}岁了`;
  }
};
obj.age = 21;
console.log(obj.age);//kevin今年21岁了

// obj.age = -1;
// console.log(obj.age);//Error: 年龄错误

访问器伪造属性操作

'use strict'
const Lesson = {
  lesson: [
    {title: 'js', click: 99},
    {title: 'ts', click: 55},
    {title: 'node', click: 77},
  ],
  set total(value){
    console.log(value);
  },
  get total(){
    return this.lesson.reduce((t,v) => t + v.click,0);
  }
}
Object.freeze(Lesson);
Lesson.total = 111;//111 这个并不是添加属性,而是在向函数传值
console.log(Lesson.total);//231

使用访问器批量设置属性

下面通过设置站网站名称与网址体验getter/setter批量设置属性的使用

'use strict'
const lesson = {
  name:'kevin',
  url: 'dd.com',
  set site(value){
    [this.name,this.url] = value.split(',')//使用数组的解构赋值和split方法
  },
  get site(){
    return `${this.name}的网址是${this.url}`;
  }
};
lesson.site = 'tom,www.niu.cn';
console.log(lesson.site);//tom的网址是www.niu.cn

下面是设置 token 储取的示例,将业务逻辑使用getter/setter处理更方便,也方便其他业务的复用。

<body></body>
<script>
  let Require = {
    set token(value){
      localStorage.setItem('token',value);
    },
    get token(){
      let token = localStorage.getItem('token');
      return !token ? '请登录' : token;
    }
  };
  Require.token = 'asdfasdf2342134234';
  console.log(Require.token);//asdfasdf2342134234
</script>

访问器的优先级

同名的访问器和属性,谁在后面声明,就会覆盖掉前面的

属性在后面

'use strict'
let user = {
  get name(){
    return 'kk';
  },
  name: 'kevin',
  age: 20
};
console.log(user);//{ name: 'kevin', age: 20 }
console.log(user.name);//kevin

访问器在后面

'use strict'
let user = {
  name: 'kevin',
  age: 20,
  get name(){
    return 'kk';
  }
};
console.log(user);//{ name: [Getter], age: 20 }
console.log(user.name);//kk

如果想要保存属性,我们可以将它定义为一个私有属性

'use strict'
const DATA = Symbol('user的私有属性');
const user = {
  [DATA]: { name: 'kk' },
  age: 20,
  set name(value){
    this[DATA].name = value;
  },
  get name(){
    return this[DATA].name;
  }
};
user.name = 'kevin'
console.log(user.name);//kevin

访问器描述符

使用 defineProperty 可以模拟定义私有属性,从而使用面向对象的抽象特性。

function User(name, age){
  let data = {name, age};
  Object.defineProperties(this, {
    name: {
      set(value){
        if(value.trim() === '') throw new Error('无效的姓名');
        data.name = value;
      },
      get(){
        return `名字是${data.name}`;
      }
    },
    age: {
      set(value){
        if(value<=0||value>100) throw new Error('年龄错误');
        data.age = value;
      },
      get(){
        return `年龄是${data.age}`;
      }
    }
  });
}
let user = new User('keivn',20);
console.log(user.name, user.age);//名字是keivn 年龄是20
[user.name, user.age] = ['tom', 22];
console.log(user.name, user.age);//名字是tom 年龄是22

上面的代码也可以使用语法糖 class定义,class是构造函数的语法糖

"use strict";
const DATA = Symbol();
class User{
  constructor(name,age){
    this[DATA] = {name, age};
  }
  set name(value){
    if(value.trim() === '') throw new Error('无效的姓名');
    this[DATA].name = value;
  }
  get name(){
    return `名字是${this[DATA].name}`;
  }
  set age(value){
    if(value<=0||value>100) throw new Error('年龄错误');
    this[DATA].age = value;
  }
  get age(){
    return `年龄是${this[DATA].age}`;
  }
}
let user = new User('keivn',20);
console.log(user.name, user.age);//名字是keivn 年龄是20
[user.name, user.age] = ['tom', 22];
console.log(user.name, user.age);//名字是tom 年龄是22

代理拦截

代理(拦截器)是对象的访问控制,setter/getter 是对单个对象属性的控制,而代理是对整个对象的控制。

  • 读写属性时代码更简洁
  • 对象的多个属性控制统一交给代理完成
  • 严格模式下 set 必须返回布尔值

其中:

Proxy怎么用

let p = new Proxy(target, handler);

语法非常简单,只有两个参数,很好理解

target

用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler

一个对象,其属性是当执行一个操作时定义代理的行为的函数。

get()

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身。所有的属性调用都会进入同一个get即使是没有的属性。

  • 可以创造一些本来没有的属性
  • 可以在取数据时对数据进行验证和转换
  • 可以自定义一些语法糖操作
  • get返回一个函数的话可以把一个属性转换成方法

set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

  • 可以用来验证属性是否符合要求
  • 可以用来更改数据格式
  • 可以用来监听数据更改事件
  • 屏蔽一些赋值操作比如以"_"开头的私有变量

示例:

"use strict";
const user = {name: 'kevin'};
const proxy = new Proxy(user, {
  get(obj, key){
    return obj[key];
  },
  set(obj, key, value){
    obj[key] = value;
    return true;
  }
});
proxy.age = 20;
console.log(user);//{ name: 'kevin', age: 20 }

代理函数

如果代理以函数方式执行时,会执行代理中定义 apply 方法。

  • 参数说明:函数,上下文对象,参数

下面使用 apply 计算函数执行时间

"use strict";
function factorial(num){
  return num===1 ? 1 : num*factorial(num-1)
}
let proxy = new Proxy(factorial, {
  apply(func, obj, args){
    console.time('run');
    func.apply(obj, args);
    console.timeEnd('run');
  }
});
proxy.apply(this,[1000]);//run: 0.161ms

数组实现代理拦截

下例中对数组进行代理,用于截取标题操作

"use strict";
const stringDot = {
  get (target, key){
    const title = target[key].title;
    const len = 5;
    return title.length > len
    ? title.substr(0, len) + ".".repeat(3)
    : title;
  }
};

const lesson = [
  {
    title: 'js的手写Promise',
    click: 88
  },{
    title: 'ts装饰器',
    click: 55
  },{
    title: 'node为什么高并发',
    click: 66
  }
];
const stringDotProxy = new Proxy(lesson, stringDot);
console.log(stringDotProxy[0]);//js的手写...
console.log(stringDotProxy[1]);//ts装饰器

使用代理拦截实现表单验证

<style>
  body {
    padding: 50px;
    background: #34495e;
  }
  input {
    border: solid 10px #ddd;
    height: 30px;
  }
  .error {
    border: solid 10px red;
  }
</style>
<body>
  <input type="text" validate rule="max:12,min:3" />
  <input type="text" validate rule="max:3,isNumber" />
</body>
<script>
  "use strict";
  //验证处理类
  class Validate {
    max(value, len) {
      return value.length <= len;
    }
    min(value, len) {
      return value.length >= len;
    }
    isNumber(value) {
      return /^\d+$/.test(value);
    }
  }

  //代理工厂
  function makeProxy(target) {
    return new Proxy(target, {
      get(target, key) {
        return target[key];
      },
      set(target, key, el) {
        const rule = el.getAttribute("rule");
        const validate = new Validate();
        let state = rule.split(",").every(rule => {
          const info = rule.split(":");
          return validate[info[0]](el.value, info[1]);
        });
        el.classList[state ? "remove":"add"]("error");
        return true;
      }
    });
  }

  const nodes = makeProxy(document.querySelectorAll("[validate]"));
  nodes.forEach((item, i) => {
    item.addEventListener("keyup", function() {
      nodes[i] = this;
    });
  });
</script>

JSON

  • json 是一种轻量级的数据交换格式,易于人阅读和编写。
  • 使用json 数据格式是替换 xml 的最佳方式,主流语言都很好的支持json 格式。所以 json 也是前后台传输数据的主要格式。
  • json 标准中要求使用双引号包裹属性,虽然有些语言不强制,但使用双引号可避免多程序间传输发生错误语言错误的发生。

序列化

序列化是将 json 转换为字符串,一般用来向其他语言传输使用。

let obj = {
  name: 'kevin',
  age: 18,
  lesson: {
    title: 'js',
    click: 66
  }
};
let json = JSON.stringify(obj, null, 2)
console.log(json);
// {
//   "name": "kevin",
//   "age": 18,
//   "lesson": {
//     "title": "js",
//     "click": 66
//   }
// }

根据第二个参数指定保存的属性

let json = JSON.stringify(obj, ['name','age'], 2)
console.log(json);
// {
//   "name": "kevin",
//   "age": 18
// }

第三个是参数用来控制 TAB 数量

let json = JSON.stringify(obj, ['name','age'], 6)
console.log(json);
// {
//       "name": "kevin",
//       "age": 18
// }

为数据添加 toJSON 方法来自定义返回格式

let obj = {
  name: 'kevin',
  age: 18,
  lesson: {
    title: 'js',
    click: 66
  },
  'JSON': function(){
    return {
      'name': this.name,
      'lesson': this.lesson.title
    }
  }
};
console.log(JSON.stringify(obj));
//{"name":"kevin","age":18,"lesson":{"title":"js","click":66}}

反序列化

使用 JSON.parse 将字符串 json 解析成对象

let obj = {
  name: 'kevin',
  age: 18,
  lesson: {
    title: 'js',
    click: 66
  }
};
let json = JSON.stringify(obj, null, 2)

let newObj = JSON.parse(json);
console.log(newObj);
// { name: 'kevin', age: 18, lesson: { title: 'js', click: 66 } }

使用第二个参数函数来对返回的数据二次处理

let obj = {
  name: 'kevin',
  age: 18,
  lesson: {
    title: 'js',
    click: 66
  }
};
let json = JSON.stringify(obj, null, 2)

let newObj = JSON.parse(json, (key, value)=>{
  if (key === 'name') return `[名字]-${value}`;
  return value;
});
console.log(newObj);
// { name: '[名字]-kevin', age: 18, lesson: { title: 'js', click: 66 } }
相关文章
|
JavaScript 前端开发
JavaScript Date(日期) 对象
JavaScript Date(日期) 对象
327 2
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
编解码 JavaScript 前端开发
【Java进阶】详解JavaScript的BOM(浏览器对象模型)
总的来说,BOM提供了一种方式来与浏览器进行交互。通过BOM,你可以操作窗口、获取URL、操作历史、访问HTML文档、获取浏览器信息和屏幕信息等。虽然BOM并没有正式的标准,但大多数现代浏览器都实现了相似的功能,因此,你可以放心地在你的JavaScript代码中使用BOM。
514 23
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
408 1
JavaScript中对象的数据拷贝
|
JavaScript 前端开发 图形学
JavaScript 中 Math 对象常用方法
【10月更文挑战第29天】JavaScript中的Math对象提供了丰富多样的数学方法,涵盖了基本数学运算、幂运算、开方、随机数生成、极值获取以及三角函数等多个方面,为各种数学相关的计算和处理提供了强大的支持,是JavaScript编程中不可或缺的一部分。
|
存储 JavaScript 前端开发
JavaScript 对象的概念
JavaScript 对象的概念
283 4
|
存储 JavaScript 前端开发
js中函数、方法、对象的区别
js中函数、方法、对象的区别
551 2
|
JavaScript 前端开发 Unix
Node.js 全局对象
10月更文挑战第5天
296 2
|
存储 JavaScript 前端开发
js中的对象
js中的对象
204 3