JavaScript權威指南(6)——物件

夕陽下的奔跑發表於2019-07-24

建立物件

  1. 物件直接量

  2. 通過new建立物件

  3. 原型

    1. 物件直接量建立的物件具有同一個原型物件——Object.prototype獲得引用
    2. 通過關鍵字new和建構函式呼叫建立的物件——建構函式的prototype屬性的值
    3. Object.prototype物件沒有原型
    4. 內建建構函式的原型繼承自Object.prototype
  4. Object.create()

    1. 第一個引數為原型
    var o2 = Object.create(null)  //o2不繼承任何屬性和方法
複製程式碼
    var o3 = Object.create(Object.prototype) //普通的空物件
複製程式碼
  1. 通過任意原型建立新物件
      function inherit(p){
      	if(p==null) throw TypeError()
      	if(Object.create){
      		return Object.create(p)
      	}
      	var t = typeof p
      	if(t != "object" && t !== "function") throw TypeError()
      	function f(){}
      	f.prototype = p
      	return new f()
      }
複製程式碼
  1. inherit()函式放置庫函式修改物件屬性。只能修改繼承來的屬性,不改變原始物件。

屬性的查詢和設定

ECMAScript 3,點運算子的識別符號不能是保留字,但是可以用方括號訪問,如object["for"].ES 5放寬了限制

  1. 作為關聯陣列的物件
    1. js物件都是關聯陣列——通過字串索引而不是數字索引
    2. for/in迴圈遍歷關聯陣列
      function getValue(object){
      	var key
      	for(key in object){
      		let val = object[key]
      		console.info(`key=${key},val=${val}`)
      	}
      }
複製程式碼
  1. 繼承
    1. 原型鏈
      var o ={}
      o.x = 1
      var p = inherit(o)
      p.y = 2
      var q = inherit(p)
      q.z = 4
      var s = q.toString() // 4 toString繼承自Object.prototype
      q.x + q.y  //3
複製程式碼
  1. 屬性查詢時會檢查原型鏈,設定屬性與繼承無關——總是在物件上建立屬性和賦值,不會修改原型鏈

  2. 例外:如果o繼承自屬性x,而這個屬性時一個具有setter方法的accessor屬性,那麼此時呼叫setter方法而不是給o建立一個屬性x——setter方法由物件o呼叫,而不是原型物件呼叫

  3. 屬性訪問錯誤

    1. 查詢不存在的屬性,返回undefined
      book.title //undefined	
複製程式碼
  1. 物件不存在會報錯,null和undefined值沒有屬性
      book.title.length //error
      
      //判空
      var len
      if(book){
          if(book.title){
              len = book.title.length
          }
      }
      //常用——&&的短路行為
      var len = book && book.title && book.title.length
複製程式碼
  1. 內建建構函式的原型是隻讀的——ES 5已修復該bug,在嚴格模式中,任何失敗的屬性設定會丟擲型別錯誤異常
      Object.prototype = o //賦值失敗,不會報錯
複製程式碼
  1. 以下場景給物件o設定屬性p會失敗
    1. o中的屬性p是隻讀(defineProperty()方法中有一個例外,可以對可配置的只讀屬性重新賦值)
    2. o中的屬性p是繼承屬性,且它是隻讀的——不同通過同名自有屬性覆蓋只讀的繼承屬性
    3. o中不存在自有屬性p:o沒有使用setter方法繼承屬性p,並且o的可擴充套件性是false

刪除屬性

  1. delete可以刪除物件的屬性
   delete object.property
   delete object["property"]
複製程式碼
  1. delete只能刪除自由屬性,不能刪除繼承(影響原型)
  2. 成功返回true,如果delete後不是屬性訪問表示式,也返回true
   //以下都返回true
   delete o.x
   delete o.x
   delete o.toString
   delete 1
複製程式碼
  1. delete不能刪除可配置性為false的屬性(可刪除不可擴充套件物件的可配置屬性)
  2. 某些內建物件的屬性是不可配置的,嚴格模式下,刪除不可配置屬性會報型別錯誤,非嚴格模式下,返回false
   delete Object.prototype	 //不可配置
   var x = 1
   delete this.x	//不能刪除
   function f(){}
   delete this.f   //不能刪除
複製程式碼
  1. 非嚴格模式下刪除全域性物件的可配置屬性時,可省略全域性物件的引用;嚴格模式會報錯

檢測屬性

  1. 通過in,hasOwnProperty()和propertyIsEnumerable()判斷某個屬性是否在某個物件中
   var o = {x:1}
   "x" in o
   "y" in o
   "toString" in o
   
   o.hasOwnProperty("x")   //自有屬性
   o.hasOwnProperty("y")
   o.hasOwnProperty("toString")
   
複製程式碼
  1. propertyIsEnumerable只有檢測到是自有屬性且這個屬性的可列舉性為true才返回true
    1. 某些內建屬性是不可列舉的
    2. 通常由JavaScript程式碼建立的屬性都是可列舉的
  2. 使用"!=="判斷屬性是否是undefined

列舉屬性

  1. for/in遍歷物件中所有可列舉的屬性
   for(p in 0){
   	if(!o.hasOwnProperty(p)) continue;   //跳過繼承的屬性
   }
   for(p in o){
   	if(typeof o[p] === "function") continue;   //跳過方法
   }
   
複製程式碼
  1. extend,merge等方法
   function extend(o, p){
   	for(prop in p){
   		o[prop] = p[prop]
   	}
   	return o
   }
   
   function merge(o, p){
   	for(prop in p){
   		if(o.hasOwnPropery(prop)){
   			continue;
   		}
   		o[prop] = p[prop]
   	}
   	return o
   }
   
   function restrict(o,p){
   	for(prop in o){
   		if(!(prop in p)){
   			delete o[prop]
   		}
   	}
   	return o
   }
   
   function subtract(o,p){
   	for(prop in p){
   		delete o[prop]
   	}
   	return o
   }
   
   function union(o,p){
   	return extend(extend({}, o), p)
   }
   
   function intersection(o, p){
   	return restrict(extend({}, o), p)
   }
   
   function keys(o){
   	if(typeof o !== "object") thorw TypeError()
   	var result = []
   	for( var prop in o){
   	 	if(o.hasOwnProperty(prop)){
   	 		result.push(prop)
   	 	}
   	}
   	return result
   }
   
複製程式碼
  1. Object.keys()返回物件可列舉的自有屬性
  2. Object.getOwnPropertyNames()返回物件自有屬性的名稱

屬性getter和setter

  1. 由getter和setter定義的屬性稱作“存取器屬性”,不同於“資料屬性”
  2. 定義存取器屬性
   var o ={
   	data_prop:value,
   	get accessor_prop() {},
   	set accessor_prop(value){}
   }
複製程式碼
  1. 存取器屬性是可繼承的
  2. 自增序列號
   var serialnum = {
    	$n:0, //私有屬性
    	get next() {
    		return this.$n++
    	},
    	set next(n){
    		if(n >= this.$n){
    			this.$n = n
    		} else {	
    			throw "..."	
    		}
    	}
   }
複製程式碼
  1. 返回隨機數
   var random = {
   	get octet() {
   		return Math.floor(Math.random() * 256)
   	}
   	get uint16(){
   		return Math.floor(Math.random() * 65536)
   	}
   	get int16(){
   		return Math.floor(Math.random() * 65526 - 32768)
   	}
   }
複製程式碼

屬性的特性

  1. 一個屬性包含名字和四個特性
  2. 資料屬性的4個特性分別是值value,可寫性writable,可列舉型enumerable和可配置性configuratble
  3. 存取器屬性不具有值value和可寫性,它的可寫性由setter方法存在與否決定——讀取get,寫入set,可列舉型和可配置性
  4. Object.getOwnPropertyDescriptor()可獲得某個物件特定屬性的屬性描述符——自有屬性
   // Returns {value: 1, writable:true, enumerable:true, configurable:true}
   Object.getOwnPropertyDescriptor({x:1}, "x");
   // Now query the octet property of the random object defined above.
   // Returns { get: /*func*/, set:undefined, enumerable:true, configurable:true}
   Object.getOwnPropertyDescriptor(random, "octet");
   // Returns undefined for inherited properties and properties that don't exist.
   Object.getOwnPropertyDescriptor({}, "x"); // undefined, no such prop
   Object.getOwnPropertyDescriptor({}, "toString"); // undefined, inherited
   
複製程式碼
  1. 設定屬性的特性Object.defineProperty——不能修改繼承屬性
   var o = {}; // Start with no properties at all
   // Add a nonenumerable data property x with value 1.
   Object.defineProperty(o, "x", { value : 1,
       writable: true,
       enumerable: false,
   	configurable: true});
   // Check that the property is there but is nonenumerable
   o.x; // => 1
   Object.keys(o) // => []
   // Now modify the property x so that it is read-only
   Object.defineProperty(o, "x", { writable: false });
   // Try to change the value of the property
   o.x = 2; // Fails silently or throws TypeError in strict mode
   o.x // => 1
   // The property is still configurable, so we can change its value like this:
   Object.defineProperty(o, "x", { value: 2 });
   o.x // => 2
   // Now change x from a data property to an accessor property
   Object.defineProperty(o, "x", { get: function() { return 0; } });
   o.x // => 0
   
複製程式碼
  1. 修改多個屬性的特性Object.defineProperties()
   var p = Object.defineProperties({}, {
       x: { value: 1, writable: true, enumerable:true, configurable:true },
       y: { value: 1, writable: true, enumerable:true, configurable:true },
       r: {
       	get: function() { return Math.sqrt(this.x*this.x + this.y*this.y) },
           enumerable:true,
           configurable:true
       }
   });
   
複製程式碼
  1. 規則:
    1. 如果物件是不可擴充套件的,則可以編輯已有的自有屬性,但不能給他新增新屬性
    2. 如果屬性是不可配置的,則不能修改他的可配置性和可列舉性
    3. 如果存取器屬性是不可配置的,則不能修改其getter和setter方法,頁不能將它轉換為資料屬性
    4. 如果資料屬性是不可配置的,則不能將它轉換為存取器屬性
    5. 如果資料屬性是不可配置的,則不能將它的可寫性從false修改為true,但可以從true修改為false
    6. 如果資料屬性是不可配置且不可寫的,則不能修改它的值。然而可配置但不可寫屬性的值是可以修改的
  2. 上文中的extend()方法沒有複製屬性的特性,改進的extend()
  Object.defineProperty(Object.prototype,
  	"extend", // Define Object.prototype.extend
  	{
          writable: true,
          enumerable: false, // Make it nonenumerable
          configurable: true,
          value: function(o) { // Its value is this function
              // Get all own props, even nonenumerable ones
              var names = Object.getOwnPropertyNames(o);
              // Loop through them
              for(var i = 0; i < names.length; i++) {
              // Skip props already in this object
              if (names[i] in this) continue;
              // Get property description from o
              var desc = Object.getOwnPropertyDescriptor(o,names[i]);
              // Use it to create property on this
              Object.defineProperty(this, names[i], desc);
          }
      }
  });
  
複製程式碼

物件的三個屬性

  1. 物件具有原型prototype,類class和可擴充套件性extensible attrubute
  2. 原型屬性——用來繼承屬性
    1. ES5:使用Object.getPrototypeOf()查詢它的原型
    2. ES3:使用obj.constructor.prototype檢測物件原型——不可靠
    3. 使用isPrototypeOf()檢測一個物件是否是另一個物件的原型,或在原型鏈中
      var p = {x:1}; // Define a prototype object.
      var o = Object.create(p); // Create an object with that prototype.
      p.isPrototypeOf(o) // => true: o inherits from p
      Object.prototype.isPrototypeOf(o) // => true: p inherits from Object.prototype
複製程式碼
      _proto_ 是Mozilla,Safari和Chrome支援,IE和Opera未實現
複製程式碼
  1. 類屬性
    1. 類屬性是一個字串,使用toString()來查詢,返回[object class]
    2. toString被重寫,故使用以下方法獲取
      function classof(o) {
          if (o === null) return "Null";
          if (o === undefined) return "Undefined";
          return Object.prototype.toString.call(o).slice(8,-1);
      }
      
複製程式碼
  1. 查詢結果

      classof(null) // => "Null
      classof(1) // => "Number"
      classof("") // => "String"
      classof(false) // => "Boolean"
      classof({}) // => "Object"
      classof([]) // => "Array"
      classof(/./) // => "Regexp"
      classof(new Date()) // => "Date"
      classof(window) // => "Window" (a client-side host object)
      function f() {}; // Define a custom constructor
      classof(new f()); // => "Object"
      
複製程式碼
  1. 可擴充套件性
    1. 用以表示是否可以給物件新增新屬性,ES5中所有的內建物件和自定義物件都是可擴充套件的
    2. 使用Object.isExtensible()來判斷是否可擴充套件
    3. 使用Object.preventExtensions()轉換為不可擴充套件——隻影響物件本身的可可擴充套件性
    4. Object.seal()除了將物件設定為不可擴充套件的,還可以將物件的自有屬性設定為不可配置的——不可解封
    5. Object.freeze(),將物件設定為不可擴充套件和屬性設定為不可配置,所有的自有資料屬性設定為只讀——存取器屬性有setter方法,則不受影響
    6. 以上方法都會返回傳入的物件
      // Create a sealed object with a frozen prototype and a nonenumerable property
      var o = Object.seal(Object.create(Object.freeze({x:1}),
      					{y: {value: 2, writable: true}}));
      
複製程式碼

序列化物件

  1. JSON.stringify()和JSON.parse()
  2. 支援物件,陣列,字串,無窮大數字,true,false和null
  3. NaN,Infinity和-Infinity序列化的結果為null
  4. 函式,RegExp,Error物件和undefined值不能序列化和還原
  5. JSON.stringify只能序列化物件可列舉的自有屬性
  6. 可接受第二個可選引數

物件方法

  1. toString()
  2. toLocaleString()
  3. toJSON()
  4. valueOf()

相關文章