Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • CSS

  • Npm

  • Vue

  • HTML

  • Node

  • Yaml

  • React

  • 框架

  • 规范

  • Electron

  • JS演示

  • VuePress

  • JavaScript

    • DOM
    • 事件
    • 实用API
    • 作用域
    • 基础篇
    • 定时器
    • 内存管理
    • 异步操作
    • 日期处理
    • 点击约束
    • 防抖节流
    • this的学习
    • ES面向对象
    • JS事件详解
    • new命令原理
    • 内置对象篇
    • 函数柯里化
    • 正则表达式
    • 浏览器模型
    • 事件订阅发布
    • 获取页面高度
    • 面向对象编程
    • JS随机打乱数组
    • js各种开发技巧
    • 函数的深入浅出
    • 防抖与节流函数
    • 模块化的使用总结
    • JS获取和修改url参数
    • JS设计模式总结笔记
    • fetch拦截和封装使用
    • 浏览器渲染原理流程
    • JavaScript对象从浅入深
      • 对象的分类
      • 创建 JavaScript 对象
      • 读取对象属性的值
      • 设置(修改)对象属性的值
      • 添加对象属性
      • 遍历对象属性
      • 删除对象的属性
      • in 运算符
      • 使用工厂方法创建对象
      • 构造函数
      • 原型prototype
      • 对象的继承
      • Object.defineProperty
        • descriptor 描述
        • 数据描述符
        • 存取描述符
      • Object.freeze
    • JavaScript高级程序设计
    • Promise使用和实现方法
    • JS的种打断点调试方式
    • js深浅拷贝详解与实现
    • 九种跨域方式实现原理
    • 多种数组去重性能对比
    • 闭包原理以及使用场景
    • 判断是否为移动端浏览器
    • 比typeof运算符更准确的类型判断
    • 搞清事件循环、宏任务、微任务
    • 将一维数组按指定长度转为二维数组
  • TypeScript

  • 微信小程序

  • TypeScript-axios

  • 前端
  • JavaScript
wangmings
2022-07-19
目录

JavaScript对象从浅入深

# JavaScript 对象从浅入深

JavaScript 中的所有事物都是对象:字符串、数值、数组、函数...

此外,JavaScript 允许自定义对象(自己创建对象)。

万物皆对象

JavaScript 提供多个内建对象,比如 String、Date、Array 等等。 对象只是带有属性和方法的特殊数据类型。

  • 布尔型可以是一个对象。
  • 数字型可以是一个对象。
  • 字符串也可以是一个对象
  • 日期是一个对象
  • 数学和正则表达式也是对象
  • 数组是一个对象
  • 甚至函数也可以是对象

# 对象的分类

1.内建对象

  • 由ES标准中定义的对象,在任何的ES实现中都可以使用
  • 比如:Math, String, Number, Boolean, Function, Object...

2.宿主对象

  • 由js的运行环境提供的对象, 目前来讲主要是由浏览器提供的对象
  • 比如BOM(浏览器对象模型) DOM(文档对象模型)

3.自定义对象 (重点难点)

  • 由开发人员自己创建的对象

# 创建 JavaScript 对象

方法1:通过 new Object(); 创建一个对象

  • 使用new关键词调用的函数,是构造函数constructor
  • 构造函数是专门用来创建对象的函数
let person = new Object();
person.name = '阿离王';
person.age = 28;
person.say = function(){
    console.log('我会说国语');
}
console.log(person);
1
2
3
4
5
6
7

方法2:也可以使用对象字面量来创建对象,语法格式如下:

{ 
    键: 值,
    键: 值
}
1
2
3
4
let person = {
    name: '阿离王',
    age: 28,
    say: function(){
        console.log('我会说国语');
    }
}
1
2
3
4
5
6
7

方法3:使用函数来构造对象

function person(name,age,say)
{
    this.name = name;
    this.age = age;
    this.say = say;
}

let my = new person("阿离王", 28, function(){ console.log('我会说国语') });
1
2
3
4
5
6
7
8

在JavaScript中,this通常指向的是我们正在执行的函数本身,或者是指向该函数所属的对象(运行时)

# 读取对象属性的值

console.log(person.name);
console.log(person['say']());
1
2

通过.属性名, 或者['属性名']的方式来读取对象属性的值

属性名可以随便起

如果要使用特殊的属性名,不能采用.的方式来操作

需要使用另一种方式:

语法:对象["属性名"] = 属性值

读取也需要用这种方式

let obj = {};
obj[123] = 456;
console.log(obj['123']);
1
2
3

使用[]这种形式去操作属性,使用更加的灵活

在[]中可以直接传递一个变量,这样变量的值是多少就会读取哪个属性

let obj2 = {
    name: 'yu',
    age: 28
};
let val = 'name';
console.log(obj2[val]);
1
2
3
4
5
6

使用[]

如果读取对象中没有的属性时,不会报错而是返回undefined

如果读取对象中没有的属性的属性时,会报错

console.log(person.haha); //undefined
console.log(person.haha.ha); // Cannot read property 'ha' of undefined
1
2

# 设置(修改)对象属性的值

person.name = '帅哥';
person.say = function(){
    console.log('我会说英语');
}
console.log(person.name);
console.log(person['say']());
1
2
3
4
5
6

# 添加对象属性

person.hobby = '跑步';
console.log(person.hobby);
1
2

# 遍历对象属性

for...in 语句循环遍历对象的属性。

语法

for (key in object)
{
    执行的代码……
}
1
2
3
4

注意: for...in 循环中的代码块将针对每个属性执行一次。

let person = {
    name: '阿离王',
    age: 28,
    say: function(){
        console.log('我会说国语');
    }
}
for (let key in person) {
    console.log(key, person[key]);
}
1
2
3
4
5
6
7
8
9
10

# 删除对象的属性

语法: delete 对象.属性名

delete person.name;
console.log(person.name); //undefined
1
2

# in 运算符

通过该运算符可以检查一个对象中是否含有指定的属性,如有则返回true,没有则返回false

语法:"属性名" in 对象

let obj3 = {name: 'yu'};
console.log('name' in obj3);
console.log('age' in obj3);
1
2
3

使用in检查对象中是否含有某个属性时, 如果对象中没有该属性,但是原型中有,也会返回true

function Person(){}
Person.prototype.haha = '哈哈';
let obj = new Person();

//使用in检查对象中是否含有某个属性时, 如果对象中没有该属性,但是原型中有,也会返回true
console.log("haha" in obj); // true
1
2
3
4
5
6

# 使用工厂方法创建对象

之前创建对象时,直接let obj = {};

let obj = {
    name: '阿离王',
    age: 28,
    sayName: function(){
        console.log(this.name);
    }
}
let obj1 = {
    name: '张三',
    age: 20,
    sayName: function(){
        console.log(this.name);
    }
}
let obj2 = {
    name: '李四',
    age: 19,
    sayName: function(){
        console.log(this.name);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

上面的例子,定义对象,每个对象的属性是不是都一样,有共同点,这样创建类似的对象就得写很多重复得代码

我们可以把他们共同的部分抽取出来,封装个工厂函数来创建对象。

来我们开始造人

/* 
 使用工厂方法创建对象
 通过该方法可以批量的创建对象
*/
function createPerson(name, age){
    //创建一个新对象
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function(){
        console.log(this.name);
    }
    return obj;
}
let obj1 = createPerson('阿离王', 28);
let obj2 = createPerson('张三', 20);
let obj3 = createPerson('李四', 19);
console.log(obj1);
console.log(obj2);
console.log(obj3.sayName());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

以上的写法是不是灵活很多,可以很方便的批量创建多个对象

# 构造函数

  • 使用new关键词调用的函数,是构造函数(constructor)
  • 构造函数是专门用来创建对象的函数
  • 构造函数就是一个普通的函数,
  • 不同的是,构造函数习惯上函数名首字母大写(不成文规范)
  • 构造函数和普通函数的区别就是调用方式的不同
  • 普通函数是直接调用,而构造函数需要使用new关键字来调用

构造函数的执行流程:

  • 1.立刻创建一个新的对象(在内存开辟一个新空间)
  • 2.将新建的对象设置为函数中的this的指向新建的对象,在构造函数中可以使用this来引用新建的对象
  • 3.逐行执行函数中的代码
  • 4.将新建的对象作为返回值返回

使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类

我们将通过一个构造函数创建的对象,称为是该类的实例, new 一个构造函数,我们称为实例化一个对象出来

this指向:

  • 1.当以函数的形式调用时,this是window
  • 2.当以方法的形式调用时,谁调用方法this就是谁
  • 3.当以构造函数的形式调用时,this就是新创建的那个对象
function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    }
}

let per = new Person('阿离王', 28);
let per1 = new Person('张三', 20);
let per2 = new Person('李四', 19);
console.log(per);
console.log(per1);
console.log(per2.sayName());

function Dog(){
}
let dog = new Dog();
console.log(dog);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

使用instanceof可以检查一个对象是否是一个类的实例

语法:对象 instanceof 构造函数

如果是返回true, 否则返回false

console.log(per instanceof Person); // true
console.log(dog instanceof Dog); // true
1
2

所有的对象都是Object的后代

所以任何对象和Object做 instanceof 检查都会返回true

console.log(per instanceof Object); // true
console.log(dog instanceof Object); // true
1
2

构造函数优化

/*
    创建一个Person构造函数
      在Person构造函数中,为每一个对象都添加了sayName方法
      sayName方法是在构造函数内部创建的,也就是构造函数每执行一次就会创建一个新的sayName方法
      这样导致了构造战术执行一次就创建一个新的方法
          执行10000次就创建1万个一模一样的方法,这个完全没必要的,浪费性能内存
          我们可以使所有的对象共享一个方法(设置全局方法)
*/
function Person(name, age){
    this.name = name;
    this.age = age;
    // this.sayName = function(){
    //     console.log(this.name);
    // }
    this.sayName = sayName();
}

// 将sayName函数在全局作用域中定义
function sayName(){
    console.log(this.name);
}

let per = new Person('阿离王', 28);
let per1 = new Person('张三', 20);
let per2 = new Person('李四', 19);
console.log(per);
console.log(per1);
console.log(per2.sayName());

console.log(per.sayName === per1.sayName); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 原型prototype

  • 我们所创建的每一个函数,解析器都会向函数添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象
  • 如果函数作为普通函数调用时,prototype没有任何作用
  • 当函数以构造函数的形式调用时,他它所创建的对象中都会有一个隐含的属性(__proto__),指向该构造函数的原型对象,我们可以通过__proto__来访问该对象的构造函数的原型对象
  • 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象
    • 我们可以将对象中共有的内容,统一设置到原型对象中
  • 当我们访问对象的一个属性或方法时,它会先去对象自身中寻找,如果有直接使用,没有则去原型链对象中寻找,找到则使用,没有就一层层往上找,直到找到Object对象(对象的祖宗), 在Object对象还找不到,则返回undefined
  • 以后我们创建构造函数时,可以将这些实例化出来的对象共有的属性和方法,统一添加到构造函数的原型中,
  • 这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了

在构造函数优化那块代码,在全局作用域定义了sayName函数,污染了全局作用域的命名空间,而且定义在全局作用域中也很不安全。

我们可以在Person的原型中添加sayName方法

/*
  将sayName函数在全局作用域中定义
  这个函数sayName在全局作用域定义,污染了全局作用域的命名空间
  而且定义在全局作用域中也很不安全 (容易给其他程序员写相同名覆盖)
*/
function Person(name, age){
    this.name = name;
    this.age = age;
    // this.sayName = sayName();
}

// function sayName(){
//     console.log(this.name);
// }

//向原型中添加sayName方法
Person.prototype.sayName = function(){
    console.log(this.name);
}

let per = new Person('阿离王', 28);
let per1 = new Person('张三', 20);
let per2 = new Person('李四', 19);
console.log(per);
console.log(per1);
console.log(per2.sayName());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • 原型对象也是对象,所以它也有原型
  • 当我们使用一个对象的属性或方法时,会先在自身中寻找
    • 自身中如果有,则直接使用
    • 如果没有则去原型对象中寻找,如果原型对象中有,则使用
    • 如果没有则去原型的原型中寻找,直到找到Object对象的原型,
    • Object对象的原型是null(没有了), 如果在Object中依然找不到,则返回undefined

这个在原型中不断的寻找原型的原型,直到Object, 这样形成一条链,我们称这条链为,原型链

使用in检查对象中是否含有某个属性时, 如果对象中没有该属性,但是原型中有,也会返回true

function Person(name, age){
    this.name = name;
    this.age = age;
    // this.sayName = sayName();
}
Person.prototype.haha = '哈哈';
let obj = new Person();
console.log("haha" in obj);
1
2
3
4
5
6
7
8

这时可以使用对象**hasOwnProperty()**方法来检查自身对象中是否含有该属性

console.log(obj2.hasOwnProperty("name"));
1

# 对象的继承

# Object.defineProperty

给对象添加一个属性并指定该属性的配置。

语法:Object.defineProperty(obj, prop, descriptor)

参数:

  • obj 要定义属性的对象。
  • prop 要定义或修改的属性的名称或 Symbol 。
  • descriptor 要定义或修改的属性描述符。

返回值:被传递给函数的对象。

# descriptor 描述

descriptor的值默认都是false

value

该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。

configurable

configurable 特性表示对象的属性是否可以被删除,以及除 value 和 writable 特性外的其他特性是否可以被修改 默认为 false。

enumerable

是否可以被枚举,(for in Object.keys) , true可以, false不可以

writable

值是否可以重写, true可以 | false(默认)

# 数据描述符

  • value
  • writable

# 存取描述符

  • get
  • set

描述符的键值value 或 writable 和 get 或 set,不能同时存在 ,否则会产生一个异常。

let obj = {};
let initValue = 'hello world';
Object.defineProperty(obj, 'key', {
    get: function () {
        // 当获取值的时候触发的函数
        return initValue;
    },
    set: function (value) {
        // 当设置值的时候触发函数,设置的新值通过value获取
        initValue = value;
    },
    // value: '值',
    // writable: true, // 值是否可以重写, true | false(默认)
    enumerable: true, // 目标是否可以被枚举 true | false(默认)
    configurable: false, // 目标属性是否可以被删除,或是否可以再次修改特性 true | false
});
//注意:get或set不是必须成对出现,如没设置方法 get set 的默认值位 undefined

console.log(obj.key); // hello world
obj.key = 'aa';
console.log(obj); // {} 使用set, get的时候,Object.defineProperty()设置的对象属性在控制台要点击展开才看到,而且比其他对象多了set, get函数
console.log(obj.key); // aa
for (const key in obj) {
    console.log(key);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

这里大家注意的就是,使用set, get的时候,Object.defineProperty()设置的对象属性在控制台要点击展开才看到,而且比其他对象多了set, get函数

# Object.freeze

方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

 let obj = {name: 'yu'};
Object.freeze(obj);
obj.name = 'yy';
console.log(obj); // {name: 'yu'} 改变不了
delete obj.name;
console.log(obj); // {name: 'yu'} 删除不了
obj.age = 28;
console.log(obj); // {name: 'yu'} 改变不了
console.log(Object.isFrozen(obj)); // true   是冻住了的对象
Object.defineProperty(obj, 'name', { // 报错
    value: 12
});
console.log(obj); 
1
2
3
4
5
6
7
8
9
10
11
12
13

被冻结的对象是不可变的。但也不总是这样。下例展示了冻结对象不是常量对象(浅冻结)。

obj1 = {
  internal: {}
};

Object.freeze(obj1);
obj1.internal.a = 'aValue';

obj1.internal.a // 'aValue'
1
2
3
4
5
6
7
8
// 深冻结函数.
function deepFreeze(obj) {

  // 取回定义在obj上的属性名
  var propNames = Object.getOwnPropertyNames(obj);

  // 在冻结自身之前冻结属性
  propNames.forEach(function(name) {
    var prop = obj[name];

    // 如果prop是个对象,冻结它
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  // 冻结自身(no-op if already frozen)
  return Object.freeze(obj);
}

obj2 = {
  internal: {}
};

deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

在javascript中, 对象冻结后, 没有办法再解冻, 只能通过克隆一个具有相同属性的新对象, 通过修改新对象的属性来达到目的.

可以这样:

ES6 Object.assign({}, frozenObject);
lodash _.assign({}, frozenObject);
1
2
编辑 (opens new window)
浏览器渲染原理流程
JavaScript高级程序设计

← 浏览器渲染原理流程 JavaScript高级程序设计→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式