04-OOP

6/22/2022

# 1. 创建对象的两种方式


如何创建一个对象呢?

  • 早期使用创建对象的方式最多的是使用Object类,并且使用new关键字来创建一个对象:这是因为早期很多JavaScript开发者是从Java过来的,它们也更习惯于Java中通过new的方式创建一个对象

  • 后来很多开发者为了方便起见,都是直接通过字面量的形式来创建对象,这种形式看起来更加的简洁,并且对象和属性之间的内聚性也更强,所以这种方式后来就流行了起来

  • 代码如下:

<script>
    // 创建方式一:new Object创建
    // Object 类  构造器  函数  函数也是类
    // 函数有多种角色:1)普通函数  2)方法   3)对象   4)类
    // 对象是属性的无序集合  操作集合  CRUD
    let obj = new Object();
    obj.name = "wc";
    obj.age = 18;
    obj.height = 1.88;
    obj.running = function(){
        console.log("running...");
    }
    console.log(obj.name);
    console.log(obj.age);
    obj.running();
    
    // 创建方式二:通常字面量的形式
    let obj2 = {
        name:"xq",
        age:20,
        eat:function(){
            console.log("eat...");
        }
    }
    console.log(obj2.name);
    obj2.eat();
</script>
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
  • JS中内置的类,如下:
<script>
    // Number是类,也叫构造器
    // 通过new运算符,得到一个对象
    // num1 百分之百是对象   var a = 110; a不是对象 a是number基本数据类型
    // 引用数据类型存储在堆区  num1中存储了地址  指向那个堆
    let num1 = new Number(110);
    console.log(num1);
    console.dir(num1); // dir是用来打印对象
</script>
1
2
3
4
5
6
7
8
9
<script>
    // String是类 也叫构造器  构造函数
    // let str1 = "hello"; // str1不是对象
    // str1.toUppercase();  str1会包装成对象
    let str = new String("hello");
    console.log(str);
</script>
1
2
3
4
5
6
7
<script>
    // let f = false; f不是对象
    let b = new Boolean(true);
    console.log(b);
</script>
1
2
3
4
5
<script>
    let obj = new Object();
    obj.name = "wc";
    console.dir(obj);
</script>
1
2
3
4
5
<script>
    let arr = new Array("wc","xq");
    console.dir(arr);
</script>
1
2
3
4
  • 补充一个运算符:instanceof 判断一个对象是否属于一个类,如下:
<script>
    let obj = new Object();
    obj.name = "wc";

    // 判断obj是否是Object的对象
    console.log(obj instanceof Object); // true
    console.log(obj instanceof Number);  // false
</script>
1
2
3
4
5
6
7
8

# 2. 一切都是对象

一切皆对象: 你需要证明,你在JS中的见到的所有的数据都是对象,这样,你才能说一切皆对象。

要证明,你见的所有数据都是对象:

- 所有的html元素都是对象
- 数组也是对象
- 函数也是对象
- 基本数据类型也可以是对象
- window也是对象  GO
- consolo也是对象
- {} 也是对象
- ...
1
2
3
4
5
6
7
8
  • 代码如下:
<ul>
    <li>html</li>
    <li>css</li>
    <li>js</li>
</ul>
<script>
    // document.getElementsByTagName("li") 得到上面的三个li
    // 得到的是一个伪数组   伪数组本质是对象
    // 真数组:["wc","xq"]  
    // 真对象:{name:"wc"}  
    // 伪数组:{0:"wc"}  {0:li,1:li,2:li}
    let lis = document.getElementsByTagName("li")
    console.log(lis);
    console.dir(lis);
    console.log("----------");
    console.log(lis[0]);
    console.log(lis[0] instanceof HTMLLIElement);
    console.dir(lis[0]);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
    // 数组是对象
    let arr = ["wc","xq"];
    console.dir(arr);
    arr.push("z3")
    console.log(arr instanceof Array);
</script>
1
2
3
4
5
6
7
<script>
    function gn(){
        console.log("gn...");
    }
    gn.a = 1;
    gn.b = 2;
    gn.c = 3;
    console.dir(gn);
    gn.call(123)
    console.log(gn instanceof Function);
</script>
1
2
3
4
5
6
7
8
9
10
11
<script>
    let obj = {
        name:"wc"
    }
    console.log(obj.name);
    console.log(obj instanceof Object);
</script>
1
2
3
4
5
6
7
<script>
    let num = 110; // num现在不是对象
    console.log(num.toFixed(2)); // num在此处就包装了对象

    let str = "hello";
    console.log(str.toUpperCase());
</script>
1
2
3
4
5
6
7
<script>
    window.alert("hello")
    window.setTimeout(()=>{console.log("定时器~");},1000)
    // console也是对象  log是这个对象中的方法
    console.log("js");
    // document也是对象  getElementById是方法
    document.getElementById("box")
</script>
1
2
3
4
5
6
7
8

# 3. 对象是属性的无序集合

对象是属性的无序集合:

  • 操作集合中的属性,说到操作,就是指"增删改查"。
  • 基本操作代码如下:
<script>
    let obj = {
        name:"wc",
        age:100,
        run:function(){
            console.log("run...");
        }
    }
    // . 属性访问运算符
    console.log(obj.name);
    console.log(obj.age);
    // [] 也可以访问对象中的属性
    console.log(obj['name']);
    console.log(obj['age']);

    // 有些情况只能使用[]  
    // 对象中的属性名存储在一个变量中
    let xxx = "name";
    console.log(obj.xxx);
    console.log(obj[xxx]);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
    // 对象中的键也可以是其它的数据类型
    // 了解 正常情况下,键都是字符串类型
    let obj = {
        123:"wc",
        true:18,
        undefined:function(){
            console.log("xxxx");
        }
    }
    // console.log(obj.123); // 不行
    console.log(obj[123]);
    console.log(obj.true);
    console.log(obj[true]);
    obj.undefined();
    obj[undefined]();
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
    let obj = {
        name:"wc",
        age:18,
        jump:function(){
            console.log("jump...");
        }
    }
    // 访问一个对象上不存在的属性,得到的结果是und
    // 数组这个对象上有length,对象上没有
    console.log(obj.length); // undefined

    // 能不能使用for循环去遍历对象
    // for(let i=0; i<obj.length; i++){}  // 不能

    // 使用for in 可以遍历对象  key表示对象中的键
    // 想获取值  不能obj.key     obj[key]
    for(let key in obj){
        console.log(obj[key]);
    }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 数组也是对象,遍历数组的方案,如下:
<script>
    let arr = ["wc","xq","z3"];

    // 遍历数组
    // 问下面的代码中有没有块级作用域,有的话,有几个?
    // 3个
    for(let i=0; i<arr.length; i++){
        console.log(arr[i]);
    }

    // forEach
    arr.forEach(item=>console.log(item))

    // for in
    for(let key in arr){
        // key是索引
        console.log(arr[key]);
    }

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
    let obj = {
        name:"wc",
        age:18
    }
    obj.name = "wc666"; // 修改obj中的属性
    console.log(obj.name);
</script>
1
2
3
4
5
6
7
8
<script>
    let obj = {
        name:"wc",
        age:18
    }
    // delete是一个运算符
    delete obj.name;
    // 访问一个对象上不存在的属性,结果是und
    console.log(obj.name);
</script>
1
2
3
4
5
6
7
8
9
10

# 4. 对属性操作的控制

前面我们对对象中的属性做了一些基本的操作,现在,学习如何精细化操作对象中的属性。在前面我们的属性都是直接定义在对象内部,或者直接添加到对象内部的:

  • 但是这样来做的时候我们就不能对这个属性进行一些限制:比如这个属性是否是可以通过delete删除的?这个属性是否在for-in遍历的时候被遍历出来呢?

如果我们想要对一个属性进行比较精准的操作控制,那么我们就可以使用属性描述符。

  • 通过属性描述符可以精准的添加或修改对象的属性;
  • 属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改;
  • Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象;

可接收三个参数:

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

返回值:

  • 被传递给函数的对象。

属性描述符分类,属性描述符的类型有两种

  • 数据属性(Data Properties)描述符(Descriptor)
  • 存取属性(Accessor访问器 Properties)描述符(Descriptor)

# 4.1 数据属性描述符

数据数据描述符有如下四个特性:

  • [[Configurable]]:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符;

    • 和数据属性描述符是一致的
    • 当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true;
    • 当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false;
  • [[Enumerable]]:表示属性是否可以通过for-in或者Object.keys()返回该属性;

    • 和数据属性描述符是一致的;
    • 当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true;
    • 当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false;
  • [[Writable]]:表示是否可以修改属性的值;

    • 当我们直接在一个对象上定义某个属性时,这个属性的[[Writable]]为true;
    • 当我们通过属性描述符定义一个属性时,这个属性的[[Writable]]默认为false;
  • [[value]]:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改;

    • 默认情况下这个值是undefined;
  • 代码如下:

<script>
    let obj = {
        name:"wc",
        age:18
    }
    // Object也是对象
    // 第三个参数是属性描述符,对height这个属性进行描述
    // 这样写,可以对height属性做精细化设置
    Object.defineProperty(obj,"height",{
        value:1.88
    })
    console.log(obj.height);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
    let obj = {
        name:"wc",
        age:18
    }
    // value 是给属性赋的值
    // configurable:配置属性是否可以删除
    Object.defineProperty(obj,"address",{
        value:"BJ",
        // configurable如果是false,表示此属性不能删除 
        configurable:false, 
        // enumerable表示属性是否可以被枚举 false表示不能枚举
        enumerable: false,
        // writable表示是否可以给属性重新赋值
        writable:false
    })
    console.log(obj.address);
    delete obj.address
    console.log(obj.address);
    for(let key in obj){
        console.log(obj[key]);
    }
    obj.address = "SH";
    console.log("----",obj.address);
</script>
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

# 4.2 存取属性描述符

数据数据描述符有如下四个特性

  • [[Configurable]]:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符;

    • 和数据属性描述符是一致的;
    • 当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true;
    • 当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false;
  • [[Enumerable]]:表示属性是否可以通过for-in或者Object.keys()返回该属性;

    • 和数据属性描述符是一致的;
    • 当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true;
    • 当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false;
  • [[get]]:获取属性时会执行的函数。默认为undefined

  • [[set]]:设置属性时会执行的函数。默认为undefined

  • 代码如下:

<script>
    let obj = {
        name:"wc",
        age:18,
        // 行业潜规则:如果属性以_打头,表示私有属性
        _address:"bj"
    }
    // 
    Object.defineProperty(obj,"address",{
        enumerable:true, // 可以枚举
        configurable:true, // 可以删除
        get:function(){ // 获取器
            // console.log("get...");
            return this._address; // this表示obj
        },
        // 当给address设置新的值时会走设置器
        set:function(value){ // 设置器  value就是设置的新值
            // console.log("set...");
            this._address = value;
        }   
    })
    // 当obj.address时,它会自动调用获取器
    console.log(obj.address); 
    obj.address = "sh"; 
    console.log(obj.address); 
</script>
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

# 4.3 同时定义多个属性

  • Object.defineProperties() 方法直接在一个对象上定义 多个 新的属性或修改现有属性,并且返回该对象。
<script>
    let obj = {
        _age:18,
    }
    Object.defineProperties(obj,{
        name:{
            value:"wc", 
            configurable:true,
            enumerable:true,
            writable:true
        },
        age:{
            configurable:true,
            enumerable:true,
            get:function(){
                return this._age;
            },
            set:function(value){
                this._age = value;
            }
        }
    })
    console.log(obj.name);
    console.log(obj.age);
    obj.age = 20;
    console.log(obj.age);
</script>
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
<script>
    let obj = {
        _age:18,
        // 在一个对象中,就可以直接写获取器和设置器
        get age(){  // 获取器
            return this._age;
        },
        set age(value){ // 设置器
            this._age = value;
        }
    }
    // 当obj.age时,会自动调用获取器
    console.log(obj.age);
    obj.age = 20;
    console.log(obj.age);
</script> 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4.4 获取属性描述符

获取对象的属性描述符:

  • getOwnPropertyDescriptor
  • getOwnPropertyDescriptors
<script>
    let obj = {
        _age: 18,  // 不想让别人访问
    }
    Object.defineProperties(obj, {
        name: {
            value: "wc",
            configurable: true,
            enumerable: true,
            writable: true
        },
        age: {
            configurable: true,
            enumerable: true,
            get: function () {
                return this._age;
            },
            set: function (value) {
                this._age = value;
            }
        }
    })
    // 获取name属性的描述符
    console.log(Object.getOwnPropertyDescriptor(obj,"name"));
    // 获取对象的所有属性的描述符
    console.log(Object.getOwnPropertyDescriptors(obj));
</script>
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

# 5. 字面量形式创建

现在要创建多个对象,有多个人,每个人都有自己的信息,此时,如何创建对象比较好?

  • 字面量形式创建代码如下
<script>
    let wc = {
        name:"wangcai",
        age:11,
        height:140,
        address:"bj",
        eating:function(){ console.log("eating..."); },
        runing:function(){ console.log("runing..."); }
    }
    let xq = {
        name:"xiaoqiang",
        age:12,
        height:150,
        address:"sh",
        eating:function(){ console.log("eating..."); },
        coding:function(){ console.log("coding..."); }
    }
    let z3 = {
        name:"zhangsan",
        age:128,
        height:180,
        address:"gz",
        playing:function(){ console.log("playing..."); },
        coding:function(){ console.log("coding..."); }
    };
</script>
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

缺点:需要写大量的代码,并且多个代码都是重复的。

# 6. 工厂函数创建对象

  • 使用工厂模式去创建对象,这是一种设计模式,这个工厂的主要作用主是产生我们需要的对象,代码如下:

<script>
    // 工厂函数  目的:产生对象
    function createPerson(name,age,height,address){
        let p = {};
        p.name = name;
        p.age = age;
        p.height = height;
        p.address = address;
        p.running = function(){ console.log(this.name + "在跑步~"); }
        return p;
    }

    let wc = createPerson("wangcai",13,160,"bj");  wc.running();
    let xq = createPerson("xiaoqiang",14,170,"gz");  xq.running();
    let zs = createPerson("zhangsan",18,180,"sz");  zs.running();

    console.log(wc instanceof Object);
    console.log(xq instanceof Object);
    console.log(zs instanceof Object);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

对象和实例是同一个意思。一个对象是属于某个类的实例。

缺点:获取不到对象最真实的类型

# 7. 使用构造器创建对象

  • 工厂函数创建对象不足:在打印出它的类型时,都是Object。我们想让每一个对象都有自己特有的类型,此时,就需要使用构造器来创建对象了。

  • 构造器,是JS中特有的,类似一个类,也叫构造函数,英文叫constructor,需要通过new一个构造器,就可以产生一个对象。

new做了什么:

  1. 在构造器内部创建一个新的对象
  2. 这个对象内部的prototype属性会被赋值为该构造函数的prototype属性;
  3. 让构造器中的this指向这个对象
  4. 执行构造器中的代码
  5. 如果构造器没有返回对象,则返回上面的创建出来的对象
<script>
    // 构造器,也就是类,首字母需要大写
    function Fn(){
        console.log("Fn...");
    }

    // Fn(); // 普通函数调用

    // ()可加可不加
    let wc = new Fn(); // obj对应的类是Fn
    console.log(wc);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
<script>
    // 构造器
    function Person(name,age,height,address){
        this.name = name;
        this.age = age;
        this.height = height;
        this.address = address;
        this.runing = function(){ console.log(this.name + " running..."); }
    }
    let wc = new Person("wangcai",19,180,"bj"); wc.runing();
    let xq = new Person("xiaoqiang",18,160,"gz");  xq.runing();
    let zs = new Person("zhangsan",15,150,"sz");  zs.runing();
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

缺点:可能造成内存空间浪费。

上面创建对象的方案都有不足之处,有没有更加优雅的方案?
答:有

Last Updated: 12/25/2022, 10:02:14 PM