【JS对象】看这一篇就够!从实践到底层JS对象详解!

目录

一、对象定义的三大方法

1、直接以对象类型定义

2、通过函数构建

3、通过Object构造器自定义

二、对象属性名和属性值的获取与删除

1、获取方法

2、属性删除

3、getters和setters

三、this引用对象

四、对象的继承

1、原型对象和原型链

2、基于原型链的继承方法

3、prototype的使用

五、对象的比较(从内存角度理解)

1、比较对象怪象

2、解释该现象


一、对象定义的三大方法

1、直接以对象类型定义

此方法即是按照我们所理解的对象形式,直接按照{}组织定义,如下:但是这样构造出来的对象无法调用其原型prototype也无法基于原型链创造孩子。

var test={
    aIndex:'1',
    bIndex:0,
    cIndex:'hello'
    displayType : function() { 
        console.log(this.aIndex);
    }
};
    

2、通过函数构建

函数方法就是将定义的函数作为对象去处理,如下:

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year; //this引用,定义属性的属性值

  //也可以定义方法
  this.displayType = function() { 
    console.log(this.make);
  }
}

var mycar = new Car("Eagle", "Talon TSi", 1993);  //创建对象实例
mycar.displayType();  //调用方法

3、通过Object构造器自定义

利用原型对象Object的构造器进行定义,如下:但是这样构造出来的对象无法调用其原型prototype也无法基于原型链创造孩子。

var myObj = new Object();

var  str = "myString",
    rand = Math.random(),
    obj = new Object();

//点 法
myObj.type              = "Dot syntax";
myObj.name              = 10;

//索引 法
myObj["date created"]   = "String with space";
myObj[str]              = "String value";
myObj[rand]             = "Random Number";
myObj[obj]              = "Object";
myObj[""]               = "Even an empty string";

console.log(myObj);

二、对象属性名和属性值的获取与删除

1、获取方法

对象的属性名相对来说好获取,属性值一般是在获取到属性名后通过obj[属性名]来获得,但也有方法可以直接获取,常用方法如下:

  • for...in 循环获取属性名   【注意:forEach只能用来遍历数组而不可以遍历对象】
    var obj = {a:1, b:2, c:3};
    
    for (var prop in obj) { 
      console.log("obj." + prop + " = " + obj[prop]);
    }
  • Object.keys(o) 获取对象中所有属性名组成的数组,Object.values(o) 则用于获取对象中所有属性值组成的数组
    //keys
    const object1 = {
      a: 'somestring',
      b: 42,
      c: false
    };
    
    console.log(Object.keys(object1)); // expected output: Array ["a", "b", "c"]
    
    
    //values
    var obj = { 0: 'a', 1: 'b', 2: 'c' };
    console.log(Object.values(obj)); // ['a', 'b', 'c']

  • Object.getOwnPropertyNames(o) 该方法返回对象 o 自身包含的所有属性 (无论是否可枚举) 的名称的数组。
    var arr = ["a", "b", "c"];
    console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
    
    // 类数组对象
    var obj = { 0: "a", 1: "b", 2: "c"};
    console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
    
    // 使用 Array.forEach 输出属性名和属性值
    Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
      console.log(val + " -> " + obj[val]);
    });
    // 输出
    // 0 -> a
    // 1 -> b
    // 2 -> c
    
    //不可枚举属性,create是利用了继承的方法,第二个参数中的方法将继承到第一个空对象参数中
    var my_obj = Object.create({}, {
      getFoo: {
        value: function() { return this.foo; },
        enumerable: false
      }
    });
    my_obj.foo = 1;
    
    console.log(Object.getOwnPropertyNames(my_obj).sort());   // ["foo", "getFoo"]

注意~:

上述方法获取到的属性都是对象本身中的,不包含原型对象/原型链中定义的其他属性和方法

下文会详细介绍原型对象和原型链

2、属性删除

delete关键字删除即可:

//Creates a new object, myobj, with two properties, a and b.
var myobj = new Object;
myobj.a = 5;
myobj.b = 12;

//Removes the a property, leaving myobj with only the b property.
delete myobj.a;

3、getters和setters

一个 getter 是一个获取某个特定属性的值的方法。一个 setter 是一个设定某个属性的值的方法。在对象中定义getter和setter,可以封装对象,实现OOP编程,减少内部成员的直接暴露。

关于getter和setter的定义方式有如下两种:

  • 直接构造访问器属性:
    var o = {
      a: 7,
      get b() {
        return this.a + 1;
      },
      set c(x) {
        this.a = x / 2
      }
    };
    
    console.log(o.a); // 7
    console.log(o.b); // 8
    
    o.c = 50;  // 可以setter修改
    
    console.log(o.a); // 25
  • 使用Object.defineProperty方法
    var book = {
        _year: 2004,
        edition: 1
    }
    
    Object.defineProperty(book, "year", {
        get: function () {
            return this._year;
        },
        set: function (newValue) {
            if (newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    });
    
    console.log(book._year + "." + book.edition);   // 2004.1
    
    book._year = 1900;
    
    console.log(book._year + "." + book.edition);   //1900.1
    
    book.year = 2005;
    
    console.log(book.year + "." + book.edition);    //2005.2
    

三、this引用对象

this指向当前对象,一般应用于与HTML语言结合使用时。我们可以将每个标签理解为一个对象,其中定义的属性都是对象中可获取的属性名和值,那么就可以与JS方法绑定应用啦~看个例子:

// 1、this指向标签自身,以参数形式传入绑定的方法中
function validate(obj, lowval, hival) {
  if ((obj.value < lowval) || (obj.value > hival)) {
    alert("Invalid Value!");
  }
}


<input type="text" name="age" size="3"
  onChange="validate(this, 18, 99)">


// 2、this.form指向父元素form(name="myForm"),this.form.text1指向父元素下name为text1的元素,所以在获取子元素时,name属性作为元素的键值

<form name="myForm">
<p><label>Form name:<input type="text" name="text1" value="Beluga"></label>
<p><input name="button1" type="button" value="Show Form Name"
     onclick="this.form.text1.value = this.form.name">
</p>
</form>

四、对象的继承

对象的继承,是基于原型(链)的继承。

1、原型对象和原型链

JavaScript 是动态的,本身不提供一个 class 的实现。即便是在 ES2015/ES6 中引入了 class 关键字,但那也只是语法糖(语法的简洁表达),JavaScript 仍然是基于原型的。所以当谈到继承时,JavaScript 只有一种结构:对象。

每个实例对象(object)都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。这种对象间的层级关系就构成了原型链, 且原型链的终端是null。

像数组(array)、字符串(string)这两个JS基本数据类型,用typeof 关键字打印出的其类型为object

var s="ssss";
console.log(typeof s);  //object

var arr=[1,2,3];
console.log(typeof arr); //object

这是因为数组和字符串的原型对象为Object,是从object对象中继承而来的。

所以如果要准确区分出数据类型究竟是数组、字符串还是对象,需要用Object.prototype.toString.call(obj)方法

console.log(Object.prototype.toString.call("jerry"));//[object String]
console.log(Object.prototype.toString.call(12));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]

function Person(){};
console.log(Object.prototype.toString.call(new Person));//[object Object]

2、基于原型链的继承方法

  • new方法

比如以下两个对象doSomething 和 doSomeInstancing之间就存在继承,其中 doSomeInstancing是通过new 一个doSomething对象的实例构造出来的,会继承doSomething对象中的属性和方法

function doSomething(){}
doSomething.prototype.foo = "bar"; // add a property onto the prototype
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomeInstancing );

查看doSomeInstancing对象结构,如下:

{
    prop: "some value",
    __proto__: {
        foo: "bar",
        constructor: ƒ doSomething(),
        __proto__: {
            constructor: ƒ Object(),
            hasOwnProperty: ƒ hasOwnProperty(),
            isPrototypeOf: ƒ isPrototypeOf(),
            propertyIsEnumerable: ƒ propertyIsEnumerable(),
            toLocaleString: ƒ toLocaleString(),
            toString: ƒ toString(),
            valueOf: ƒ valueOf()
        }
    }
}

可见原型链由下至上为 doSomeInstancing ->  doSomething -> Object

  • 使用Object.create()方法基于原型创建新对象

该方法是基于现有对象为原型创建新的对象,语法参数如下:

Object.create(proto)
Object.create(proto, propertiesObject)

 分别举一个例子:

// 无第二个参数
var a = {test: 1};

var b = Object.create(a);   //原型链为: b ---> a ---> Object.prototype ---> null
console.log(b.test); // 1 (从对象a中继承而来)


//有第二个参数
var o = Object.create(null, {
  foo: {
    writable: true,
    configurable: true,
    value: 'hello'
  },
  bar: 'myBar',
});

3、prototype的使用

下述例子中,通过 prototype属性给之前定义的Car对象类型增加color属性。这为继承Car类型的所有对象都增加了color属性。但如果子对象自己又定义了color属性的属性值,则会覆盖之前继承而来的color属性值。

function Car(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year; //this引用,定义属性的属性值

    //也可以定义方法
    this.displayType = function () {
        console.log(this.make);
    }
}

Car.prototype.color = "black";    //原型对象加属性

var mycar = new Car("Eagle", "Talon TSi", 1993);  //Car类型实例1
mycar.color = "white";
console.log(mycar.color);    //输出white  

var t = new Car("Eagle", "Talon TSi", 1993);  //Car类型实例2
console.log(t.color);       //输出black  color属性继承而来

五、对象的比较(从内存角度理解)

1、比较对象怪象

在 JavaScript 中 objects 是一种引用类型两个独立声明的对象永远也不会相等,即使他们有相同的属性,只有在比较一个对象和这个对象的引用时,才会返回 true.

// 两个变量,两个具有同样的属性、但不相同的对象
var fruit = {name: "apple"};
var fruitbear = {name: "apple"};

fruit == fruitbear // return false
fruit === fruitbear // return false

如果为引用,

// 两个变量,同一个对象
var fruit = {name: "apple"};
var fruitbear = fruit;  // 将 fruit 的对象引用(reference) 赋值给 fruitbear
                        // 也称为将 fruitbear“指向”fruit 对象
// fruit 与 fruitbear 都指向同样的对象
fruit == fruitbear // return true
fruit === fruitbear // return true

2、解释该现象

导致上述现象的主要原因是对象其实赋值给变量的不是值,而是内存地址。因此我们比较对象时不仅比较内容,还比较着变量地址。这就是对象作为引用类型的特别之处。

我们知道,JS中有七种基本数据类型(number string object等等),这七种基本数据类型又可以按照存储方式分为两大类型:原始类型和引用类型。这两个究竟有啥区别呢?

首先我们要了解,JS内存分为三大块:代码空间、栈空间、堆空间。而原始类型保存在栈空间,引用类型保存堆空间。这就导致我们定义变量时,Javascript引擎将自动把不同类型的变量分配到合适的空间内。

如,有以下代码:

 解析后变量环境存储情况为:

猜你喜欢

转载自blog.csdn.net/weixin_57208584/article/details/126727708
今日推荐