第一:Javascript對象是
第二:Javascript中
第三:Javascript的對象是數據;
第四:JavaScript 中的對象可以簡單理解成"名稱:值"對(name:value)。名稱(name):"名稱"部分是一個 JavaScript 字符串
參考----------
對象是JavaScript的一個基本數據類型,是一種複合值,它將很多值(原始值或者其他對象)聚合在一起,可通過名字訪問這些值。即屬性的無序集合。
二、對象的創建(多種方法)
1、對象直接量 / 字面量
var obj = {
name: 'lyl',
age: 18
}
console.log(obj.name); // lyl
2、構造函數:
(1)、系統自帶的的, eg: new Object(), Array(), Number(),Boolean(), Date()...
var obj = new Object();
obj.name = 'lyl';
console.log(obj.name); //lyl
(2)、自定義的:為了和普通函數區分,首字母大寫,採用大駝峯式寫法(普通函數採用小駝峯式寫法)
this.name = name;
this.age = 18;
}
var obj = new Obj('lyl');
console.log(obj.name); //lyl
console.log(obj.age); //18
自定義構造函數的基本構造原理:
首先,文字理論解釋一番,其實一切的關鍵全在與new這個操作符上,用new和不用new返回的結果大相徑庭。不用new,則Obj('lyl')根本就是一個函數的正常執行,沒有返回值,則默認返回undefined,而是用new操作符後js引擎就會將該函數看作構造函數看待,經過內部的一系列隱士操作,返回值就是一個對象了。
看下如下demo:
demo1:用new和不用new的區別演示:
function Obj () {
this.age = 18;
}
// 不用new
console.log(Obj()); // undefined
// 用new
console.log(new Obj()); //Obj {age: 18}
demo2 用new返回值為對象的基本原理:
不用new,函數內的this指向的是window,所以this.xxx定義的變量都是window上的屬性,但為什麼使用new後其中的this就不是window對象了呢?那是因為
var this = Object.create(Peson.prototype); (也是創建對象的一種方法,下邊會講到) 隱士的改變函數內this的含義,現在函數內的this是一個原型為Person.prototype, 構造函數為Person的對象(其實此過程就將想要的對象基本創造成功了,只是差些屬性而已,從此可是看出構造函數創建對象的最根本原理是借用Object.create()方法來實現的,只不過被封裝功能化了); 第二步, 在創建的對象設置完所需要的屬性後,隱士的將創建的對象this通過return返回 return this;
通過代碼的展現:
// 構造函數的原型
Person.prototype = {
say: function () {
console.log('I am saying');
}
}
// 構造函數
function Person () {
// 隱士操作
// var this = Object.create(Person.prototype);
//返回對象屬性的設置
this.name = 'lyl';
this.age = 18;
// 隱士操作
// return this;
}
var person1 = new Person();
console.log(person1.name); // lyl
person1.say(); //I am saying
上述兩步理論的驗證:
第一步:現在函數內的this是一個原型為Person.prototype, 構造函數為Person的對象
// 構造函數的原型
Person.prototype = {
say: function () {
console.log('I am saying');
}
}
// 構造函數
function Person () {
this.name = 'lyl';
this.age = 18;
// 打印this對象的原型
console.log(this.__proto__); //
// 驗證this是否是Person構造函數的實例
console.log(this instanceof Person); //true
}
new Person();//打印結果如下
// Object say: ()__proto__: Object
// true
Person();//打印結果如下
// Window
// false
第二步:隱士的將創建的對象this通過return返回
由以上一些代碼,我們已經可以看出返回的是滿足條件的對象,現在我們創建對象時不用new,並顯示的模擬這兩步隱士操作來驗證(我們不用new則兩步隱士操作就不會產生)
// 構造函數的原型
Person.prototype = {
say: function () {
console.log('I am saying');
}
}
// 構造函數
function Person () {
var that = Object.create(Person.prototype);
that.name = 'lyl';
that.age = 18;
return that;
//提前返回that導致return this無法執行而失效
}
var person = new Person();
//此處不用new也是可以成功返回一個滿足條件的對象,因為顯示的返回了that
console.log(person.name); //lyl
person.say(); //I am saying
p.s. 關於顯示返回that的問題,當我們用new生成對象,若我們顯示return的是一個對象 / 引用值,則會導致return this失效,若返回的是原始值,則return this不會失效
3、Object.create(原型); 創建一個繼承該原型的實例對象
關於此方法的一些注意事項:
Object.create(Object.prototype) <==>new Object();
(2)、若傳參為空 或者 null,則創建的對象是沒有原型的, 導致該對象是無法用document.write()打印會報錯,因為document.write()打印的原理是調用Object.prototype.toString()方法,該對象沒有原型,也就沒有該方法,所以document.write()無法打印
由此延伸的知識點: 引用值都也是算作是對象,所以都可以用document.write()打印;原始值numebr, boolean, string都有自己對象的包裝類,藉助此機制也是可以用document.write()打印出的;
但undefined 和 null既不是引用值,也沒有對應的包裝類,所以應該無法打印的,但大家會發現這兩個值也是可是用document.write()打印的,因為這兩個值被設定為特殊值,document.write()打印其是不用調用任何方法的,而是之直接打印其值
三、對象的增、刪、改、查
1、增: 所謂增添一個對象的屬性,就是直接對該屬性進行賦值操作即可,這就相當於為該對象添加了一個新屬性,而打印未添加的屬性,瀏覽器不會報錯,而是會打印出undefined
var obj = {};
console.log(obj.name); //undefined (不會報錯)
obj.name = 'lyl';
console.log(obj.name); // lyl
2、刪:我們通過delete操作符來刪除一個對象的屬性
var obj = {
name : 'lyl'
};
console.log(obj.name); //lyl
delete obj.name;
console.log(obj.name); //undefined
3、改: 修改一個對象的屬性是最簡單的了,直接通過賦值操作賦予其其他的值即可
var obj = {
name: 'lyl'
};
console.log(obj.name); // lyl
obj.name = 'obj';
console.log(obj.name); // obj
4、查:查詢一個對象的屬性值有兩種方法
var obj = {
name: 'lyl'
};
// 第一種方法
console.log(obj['name']); //lyl
// 第二種方法
console.log(obj.name); // lyl
//p.s.最本質的是第一種方法,因為在使用第二種方法時,後台自動將其轉換為第一種字符串的形式來查詢
p.s.以上的增、刪、改三種操作都只是針對當前對象的屬性進行操作,而不會影響到當前對象的原型的屬性。
而查詢是先看看當前對象本身是否設置了該屬性,如果當前對象未設置該屬性,則再看該對象的原型中是否設置了該屬性,若兩者都沒有,則返回undefined
四、包裝類:
1、五個原始值:number, string , boolean, undefined, null
其中number, string, boolean是分別擁有自己的包裝類,而undefined和null是沒有自己的包裝類的
2.原始值不是對象,無法擁有自己的屬性,但因為的包裝類的存在,原始值就好似可以擁有自己的屬性了,但其擁有的屬性又有點特殊之處,如下用string來舉例:
先看一段code
// str是string類型的,非對象,不能擁有屬性,為什麼能打印出str.length?
var str = 'abcd';
console.log(str.length); //4
上邊code中問題的解釋:
// 因為每次執行完一條完整js語句後該類型對象的包裝類就會將該語句包裝,所以也就不會導致報錯了,這些都是後台自己寫的 var str = 'abcd'; // var str1 = new String('abcd'); console.log(str.length); //4 // var str1 = new String('abcd'); // console.log(str1.length);
注意:每次包裝類包裝完一次完整語句後就會被銷燬。(即解釋一條語句,用包裝類包裝一次,然後銷燬),這回導致其和正常對象的一些不同之處,如下例子
var str = 'abcd';
str.len = 4;
console.log(str.len); // undefiend
//???
關鍵在於‘銷燬’一詞上,解釋如下:
var str = 'abcd';
// var str1 = new String('abcd');
// 銷燬
str.len = 4;
// var str1 = new String('abcd');
// str1.len = 4;
// 銷燬
console.log(str.len); // undefiend
// var str1 = new String('abcd');
// console.log(str.len); str1為剛創建的對象,其len屬性自然為undefiend
// 銷燬
一個易錯的坑:
// 總之記得'原始值包裝類''銷燬'這兩句話就行
var str = 'abcd';
str.lenth = 2;
console.log(str); // ab or abcd ? answer is abcd
console.log(str.length); // 2 or 4 ? answer is 4
五、原型:
1、原型的定義: 原型是function對象的一個屬性,它定義了構造函數製造出的對象的公共祖先。通過改構造函數產生的對象,可以繼承該原型的屬性和方法。原型也是對象。
2、利用原型特點和概念,可以提取共有屬性。將一類對象的共有屬性提取出來,放到該類對象的原型中,從而不需要每次用new操作符時都重新定義一遍該共有屬性。
如下,定義一個Person構造函數,而屬於Person多構造對象共有的屬性方法,則定義到Person的原型中
Person.prototype = {
eat: function (food) {
console.log('I have eated ' + food);
},
sleep: function () {
console.log("I am sleeping");
}
}
// 人的構造函數
function Person (name, age) {
this.name = name;
this.age = age;
}
var person1 = new Person('lyl', 18);
console.log(person1.name); //lyl
person1.eat('apple'); //I have eated apple
3、如何查看原型:
之前是不允許我們查看構造函數的原型的,但後來提供了一個可查看構造函數原型的接口:隱士屬性__proto__(其實我們能夠訪問原型的屬性,或者説繼承原型,靠的就是__proto__屬性連接着構造函數和原型,可以説沒有__proto__屬性的存在,就無法實現原型的繼承)
(1)、首先我們先説明一下__proto__這個接口是存放到哪裏的
看過以上對象創建過程的都應該知道在用new創建一個對象時,內部會隱士自動創建一個this的對象,進過一系列處理後再隱士將this對象返回。而__proto__就對於隱士創建的this對象中,如下代碼:
// 原型
Person.prototype = {
say: function () {
console.log("I am saying ");
},
play: function () {
console.log("I am playing");
}
}
// 構造函數
function Person (name) {
// var this = Object.create(Person.prototype);
// p.s.在隱士創建的this對象中存在一個屬性,即__proto__,該屬性存儲了Person.prototype
this.name = name;
// return this;
}
// 對象的創建
var person1 = new Person('lyl');
// 打印原型
console.log(person1.__proto__);
(2)、如何查看原型:直接通過new操作符創建的對象訪問__proto__屬性即可,如上代碼演示
4、如何查看對象的構造函數,我們通過屬性constructor來查看:
contructor屬性位於構造函數的原型中,其中存儲的是構造函數信息,所以在不知道原型的情況下,由原型繼承原理,我們可以用實例對象來直接訪問constructor,即獲取創建該實例的構造函數
function Person () {
this.name = 'myName';
this.age = 18;
}
var person = new Person();
console.log(person.constructor); // function Perso(){...}
六、原型鏈:
1、定義:顧名思義,原型鏈就是將一個個原型串連起來,形成一條原型繼承的鏈子。
2、原型鏈的構成:
如下代碼例子, Child繼承Parent, Parent繼承GrandParent, 而GrandParent沒有自定義原型,所以默認為原型鏈的最頂端new Object();
(為什麼Object為最頂端,因為Object.prototype為null,為null是沒有原型的)
// 原型鏈: Child -> new Parent() -> new GrandParent() -> new Object();
function GrandParent() {
this.name = 'GrandParent';
this.a = 3;
}
Parent.prototype = new GrandParent();
function Parent() {
this.name = 'parent';
this.b = 2;
}
Child.prototype = new Parent();
function Child() {
this.name = 'child';
this.c = 1;
}
var child = new Child();
console.log(child); // Child {name: "child", c: 1}
console.log(child.a); // 3
console.log(child.b); //2
console.log(child.c); //1
3、原型鏈的增刪改查:
使用如上的原型鏈説明, Child -> new Parent() -> new GrandParent() -> new Object(), 實例對象為child
(1)、增:
為child實例對象添加屬性,總是添加為其自己本身的屬性,為對原型和原型鏈是沒有影響的。(再具體説即對和其相同構造函數構造的實例對象無法造成影響,以下説法同此)
(2)、刪:
使用delete操作符只能刪除child實例對象自己本身的屬性,而無法刪除由原型繼承而來的屬性
(3)、改:
分兩種情況:
若修改的屬性為繼承自原型的,且值類型為原始值,則僅僅修改的是該實例對象的屬性,對原型無法造成影響。
若修改的屬性為繼承自原型的,屬性值類型為引用值,則對引用值的修改又分兩種情況:
第一種是直接對該修改的屬性賦值 => 此情況僅僅修改的是實例對象的該屬性,無法對原型造成影響。
第二種是對該修改的屬性添加內容或去除內容,而不是對其重新賦值 => 此情況會對原型造成影響。如下例子:
Person.prototype = {
has: [1, 2, 3]
}
function Person () {
this.name = 'lyl';
}
var person1 = new Person();
var person2 = new Person();
person1.has.push(4);
// person1 和 person2都改變了,因為person1的修改影響到了原型,進而影響到了另一個實例對象
console.log(person1.has); //[1, 2, 3, 4]
console.log(person2.has); // [1, 2, 3, 4]
(4)、查:
查詢過程如下,首先看構造函數中是否有要查詢的屬性,若有,則直接返回,若沒有,則看其原型有沒有要查詢的屬性,若沒有,則再看原型的原型上是否有要查詢的屬性,以此順序在原型鏈上查詢,若一直到原型鏈頂端後仍沒有要查詢的屬性,則返回undefined
p.s.理解了在原型鏈上的增刪改查後,自然就能理解在原型上的增刪改查了,只要把在原型上的增刪改查當成只有一個原型的很短的原型鏈即可。
4、絕大多數對象最終都會繼承自Object.prototype
為什麼事絕大多數呢?因為null,和undefined是沒有原型的,上文有詳細提到
七、對象繼承史
由於之前已經總結過這些,所以此處直接就粘鏈接了:
八、命名空間:
我們可以利用對象創建命名空間來管理變量,防止污染全局,適用於模塊開發,如下簡單的小demo:
var workSpace = {
person1: {
name: 'one',
age: 18
},
person2: {
name: 'two',
age: 20
}
}
// 這樣兩個人雖然有同名變量,但不會相互影響,因為位於不同命名空間
// 訪問第一個人的姓名
console.log(workSpace.person1.name); // one
console.log(workSpace.person2.name); //two
九、實現類似jquery中的鏈式調用: return this;
如下小demo,其中的this不懂的沒關係,上面會説,你只要把次demo中的this當成person對象就行
foodCount: 10,
eat: function () {
this.foodCount--;
return this;
},
buy: function () {
this.foodCount++;
return this;
},
print: function () {
console.log(this.foodCount);
}
}
// foodCount初始值為10, 在連續吃了三次後,變為7
person.eat().eat().eat();
person.print(); //7
十、對象的枚舉:
1.obj.hasOwnProperty('prop');
該方法的作用是來判斷對象obj的自身屬性中是否含有屬性prop,
自身屬性是在構造函數中生成的或者實例對象後來自己添加的,而繼承屬性則是從原型上繼承的屬性,
所以該方法就是判斷該屬性是從原型繼承來的還是自身的。
作用: 遍歷一個對象的所有自身屬性,因為es5中的對象遍歷是默認打印包括繼承自原型的屬性的,demo如下
function Person () {
this.name = 'lyl';
}
var person = new Person();
// 未用hasOwnProperty
// print:
// lyl
for(var prop in person) {
console.log(person[prop]);
}
// 使用hasOwnProperty
// print:
// 18 lyl
for(var prop in person) {
if(person.hasOwnProperty(prop)) {
console.log(person[prop]);
}
}
2、prop in obj;
in操作符用來判斷該對象obj上是否有該屬性prop,prop既可以是自身屬性,也可以是繼承屬性,如下demo
function Person () {
this.name = 'lyl';
}
var person = new Person();
console.log('age' in person); // true
console.log('name' in person); //true
delete person.name;
console.log('name' in person); //false
3、object instanceof Object;
instanceof操作符用來判斷object實例對象是否為Object構造函數創建的,如下demo
function Person () {
this.name = 'lyl';
}
var person = new Person();
console.log(person instanceof Person); // true
console.log(new Object() instanceof Person); //false
十一、this基本介紹:
1、函數預編譯過程 this —> window
2、全局作用域裏 this —> window
3、obj.func(); func()裏面的this指向obj), 可以這樣理解,誰調用func,則this就指向誰
4、call/apply 可以改變函數運行時this指向,
(1)、call用法:
func.call(要改變後的this, arg1, arg2, ... );
(2)、apply用法:
func.apply(要改變後的this, [arg1, arg2, arg2]);
(3)、apply和call共同點:都是改變this指向
apply和call不同點:傳參形式不同,call是將參數一個個傳進來,而apply是將所有參數存進一個數組中,然後將該數組傳
如下demo:
function demo1() {
console.log(this);
}
// demo1() <==> this.demo1(); <==> window.demo1()
demo1(); // window
// demo2
var demo2 = {
retThis: function () {
console.log(this);
}
}
demo2.retThis(); // demo2 = {...}
// call / apply改變this
demo1.call(demo2); // demo2 = {}
demo2.retThis.call(window); // window
十二、對象的克隆:
你可能回想,直接用等號賦值不久完成克隆了嗎?這還用説,但你忽略了對象是一個引用值,賦值操作賦的是該對象的引用,然後會產生很壞的影響。
舉例來説,將obj1賦值給obj2,然後我們向obj2中添加了一個屬性,然後我們會驚喜的發現obj1中也有了此剛剛向obj2中添加的屬性,但我obj1並不需要該屬性啊,則造成obj1不開心了,為了讓obj1開心起來,我們向下看,嘿嘿:
對象克隆,分為淺克隆和深克隆,而上邊的直接賦值的克隆操作為淺克隆,為什麼稱為淺克隆呢?因為,克隆的和被克隆的對象在克隆操作完成後,指向同一個地址引用,改變其中一個(注意:此處的改變為增加或刪除對象的屬性,而不是為該對象重新賦值一個對象),另一個也會改變,而深克隆則不會產生此現象。
深克隆/深拷貝code如下,這是我從網上搜到的一個目前我見到的最完整最簡短的code,直接貼上:
// 對象的深度克隆(Array / obj /...)
function deepClone(obj) {
var str, retObj = Object.prototype.toString.call(obj) === '[object Array]' ? [] : {};
if(typeof obj !== 'object') {
return;
}else if(window.JSON) {
str = JSON.stringify(obj);
retObj = JSON.parse(str);
}
else {
for(var prop in obj) {
retObj[prop] = typeof obj[prop] === 'object' ? deepClone(obj[prop]) : obj[prop];
}
}
return retObj;
}
1、通過對象字面量的方式創建對象(對象字面量是一個表達式,這個表達式的每次運算都創建並初始化一個新對象。每次計算對象字面量的時候,也都會計算他的每個屬性的值。也就是説,如果在一個重複調用的函數中的循環體內使用了對象直接量,它將創建很多新對象,並且每次創建的對象的屬性值也有可能不同。)
語法:var obj = {};
實例:
首先,我們創建一個空的對象,裏面沒有任何屬性,我們來獲取它的類型
var obj1 = {};//沒有任何屬性的對象
console.log(typeof obj); //object
它返回的結果是object,説明我們創建的是一個object對象,當然,我們還可以用下面的方法來創建
var obj2={x:1,y:2,z:3};
var obj3={ 'x':1, "y":2, username:'king', 'for':'javascript關鍵字必須放到引號中間', //for是javascript關鍵字,必須放到引號中間 'first-name':'foodoir', //-是特殊字符,也需要放在引號中間 married:true, test:null, test1:undefined, salary:12.3, person:{ //在對象中"值"部分可以是任何 JavaScript的數據類型——包括對象 username:'king', age:21, addr:'北京', }, //最後一個對象屬性的後面的逗號可以寫可以不寫,在ECMAScript 5中自動被忽略,在ECMAScript 3的大部分實現中也可以忽略這個逗號,但在IE中報錯 }
2、通過new object創建對象
語法:var obj = new Object();
實例:
var obj4 = new Object();//創建一個空對象
var arr = new Array();//創建一個空數組對象 var date = new Date();//創建一個空時間對象
3、通過構造函數的形式創建對象
語法:function Person(){};或者var Person=function(){};
實例:
var obj5=new Test1();
function Test1(num1,num2){
this.n1=num1;
this.n2=num2; } var obj6=new Test1(5,6); console.log(typeof obj6); //object console.log(obj6 instanceof Object); //true
在使用通過構造函數的形式創建對象時應注意:
a.使用的時候通過new操作符得到對象var person1=new Person()
b.用構造器創建對象的時候可以接收參數
c.構造器函數的首字母最好大寫,區別其他的一般函數
注意:構造器屬性(constructor property),當我們創建對象的時候,實際上同時也賦予了該對象一種特殊的屬性,就是構造器屬性,這個構造器屬性實際上是一個指向用於創建該對象的構造器函數的引用
補充:
typeof 和 instanceof 常用來判斷一個變量是否為空,或者是什麼類型的。但它們之間還是有區別的:
typeof 是一個一元運算,放在一個運算數之前,運算數可以是任意類型。
它返回值是一個字符串,該字符串説明運算數的類型。typeof 一般只能返回這幾個結果:number,boolean,string,function,object,undefined。
我們可以使用 typeof 來獲取一個變量是否存在,如 if(typeof a!="undefined"){alert("ok")},而不要去使用 if(a) 因為如果 a 不存在(未聲明)則會出錯,對於 Array,Null 等特殊對象使用 typeof 一律返回 object,這正是 typeof 的侷限性。
instanceof 用於判斷一個變量是否某個對象的實例
在上面的例子中,obj6 instanceof Object判斷的為true,則説明創建的obj6也是Object類型
4、通過Object.create()創建對象
var obj7 = Object.create({x:1});
//創建一個普通的空對象
var obj8 = Object.create(null); //創建一個對象的原型屬性 var obj9 = Object.create(Object.prototype); //prototype對象的原型屬性 console.log(typeof obj7); //object console.log(typeof obj8); //object console.log(typeof obj9); //object //通過instanceof 操作符檢測對象是否由某個指定的構造器函數創建的 console.log(obj7 instanceof Object); //true console.log(obj8 instanceof Object); //false,注意:空對象用instanceof判斷時,結果為false console.log(obj9 instanceof Object); //true
如何對對象進行操作?
學過數據庫的我們都知道,數據庫最基本的四個操作分別是“增、刪、查、改”,那麼在對對象的操作中是否也存在類似的操做呢?是的,在對對象的操作中也有查詢,添加,修改,刪除操作,都是基於對象的屬性的操作。下面我們來介紹對象的幾個基本操作。
首先,我們先要通過查詢方法,來獲取對象中的屬性,訪問屬性有三種方法:對象名.屬性名;對象名[屬性名];當處於某個對象方法內部的時候,可以通過this來訪問同一對象的屬性
var person = {
username: "foodoir", age: 21, sex: "男", addr: "湖南", salary: 123456, }; console.log("姓名:" + person.username + "\n" + "性別:" + person.sex); //姓名:foodoir 性別:男 console.log("年齡:" + person['age'] + "\n" + "薪水:" + person["salary"]); //年齡:21 薪水:123456 //如果屬性不確定,需要使用[] var key = 'username'; console.log(person.key); //undefined 此種方法不能得到key的值,通過[]方法可以得到 console.log(person[key]); //foodoir 當屬性不確定時,用該方法 console.log(person['key']); //undefined
看到這裏,你肯定會有有疑問,為什麼第9行代碼中person['age']中的age和person["salary"]中的salary都加了引號,且都可以顯示出結果,而在第13行代碼中person['key']中的key加了引號,反而返回值是undefined 原因究竟是什麼呢?
age為person對象中的屬性名,而該對象只對person進行了定義,沒對person裏面的屬性進行定義,故在調用的時候,在[]中間應該加上引號,也就是説,person['age']和person.age是等價的
而key是在對象外定義的(var key = 'username';),在對象裏面它是不存在key這個屬性的(即屬性不確定),此時直接用person[key]方法就好了,person.key和person['key']返回的必須是person對象裏面確定的屬性,key沒在person屬性裏面,故這兩種方法返回的值都是undefined
學會了如何獲取對象的屬性,然後我們該繼續學習對象中的屬性進行添加,修改和查詢操作
function personInfo(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex; } var person1 = new personInfo('foodoir', 20, '男'); console.log(person1.name + person1.sex); //foodoir男
屬性的添加有兩種方式:對象名.屬性名=值;對象名[屬性名]=值
//添加屬性
var obj = {};
obj.username = 'foodoir'; obj.age = 21; obj.sex = '男'; obj.addr = '湖南'; obj['test'] = 'hello world'; console.log(obj.username + " " + obj.age + " " + obj.sex + " " + obj.addr + " " + obj['test']); //foodoir 21 男 湖南 hello world
屬性的修改也有兩種方式:對象名.屬性名=值;對象名[屬性名]=值
//修改指定屬性
obj.username = 'chenwu'; console.log("修改之後的名字:" + obj.username); //修改之後的名字:chenwu obj['test'] = 'hello javascript'; console.log("修改之後的test:" + obj['test']); //修改之後的test:hello javascript
屬性的刪除也有兩種方式:delete 對象名.屬性名;delete 對象名[屬性名] 。在delete刪除指定的屬性時應該注意:(後面講對象的結構的時候會詳細介紹)
delete只能刪除自身屬性,不能刪除繼承屬性
要刪除繼承屬性,只能從定義它屬性的原型對象上刪除它,而且這會影響到所有繼承自這個原型的對象
delete只是斷開屬性和宿主對象的聯繫,而不會去操作屬性的屬性
delete不能刪除哪些可配製性為false的屬性
//通過delete刪除指定的屬性
delete obj.sex;
console.log(obj.sex); //undefined
delete obj['test']; console.log(obj['test']); //undefined
除了“增、刪、改、查”我們還要學會對象中的遍歷,對象中的遍歷有兩種,一種是for/in遍歷,一種是通過Object.keys(obj)函數進行遍歷
var obj = {
x: 1,
y: 2, test: "helloworld", edu: "javascript", }; //通過for/in遍歷屬性 for(var i in obj) { console.log(i); //x y test edu(換行輸出) console.log(obj[i]); //輸出的是屬性名對應的屬性值 } //通過Object.keys(obj)遍歷屬性 console.log(Object.keys(obj)); //["x", "y", "test", "edu"]
Object.keys() 方法會返回一個由給定對象的所有可枚舉自身屬性的屬性名組成的數組,數組中屬性名的排列順序和使用for-in循環遍歷該對象時返回的順序一致(兩者的主要區別是 for-in 還會遍歷出一個對象從其原型鏈上繼承到的可枚舉屬性)。
學了前面的覺得難度不是很大,我們試着做一個對象中有方法的例子
var obj = {
username: 'foodoir', age: 21, sex: '男', sayHi: function() { return 'say hi'; }, info: function() { return obj.username + obj['sex']; } } console.log(obj.sayHi()); //say hi console.log(obj.info); //在info的後面要加(),不然結果是:function(){ return obj.username+obj['sex']; } console.log(obj.info()); //foodoir男
注意:在方法的後面要加(),不然,返回的將是function(){ return obj.username+obj['sex']; }
function Person(username, age, sex) {
this.username = username;
this.age = age;
this.sex = sex; this.info = function() { return this.name + this.age; } } var person1 = new Person('foodoir', 21, '男'); console.log(person1.info()); //NaN String+Number兩種類型相加的結果為NaN console.log(Person.person1); //undefined person1不是Person裏面的屬性,故返回undefined console.log(person1.username); //foodoir //向對象中添加新屬性 person1.test = 'this is a test'; //this is a test console.log(person1.test); //向對象中添加新的方法 person1.fun = function() { return 'hello world'; } console.log(person1.fun()); //hello world
到此刻,你可能有新的疑問,究竟對象的結構是怎樣?下面圖片是我自己對於對象的理解畫的一幅圖
與對象相關的特性有哪些?
與對象相關的特性有三個:對象的原型(prototype)、對象的類(class)、對象的擴展標記(extensible flag),具體如下:
對象的原型(prototype)指向另外一個對象,本對象的屬性繼承自它的原型對象
通過對象字面量創建的對象使用Object.prototype作為它們的原型
通過new創建的對象使用構造函數的prototype屬性作為他們的原型
通過Object.create()創建的對象使用第一個參數(也可以是null)作為它們的原型
關於對象的原型,前面已經介紹過了,這裏不再另外介紹。
對象的類(class)是一個標識對象類型的字符串
ECMAScript3和ECMAScript5都未提供這個屬性的方法,可以通過對象的toString()方法間接查詢。下面來看例子:
var obj = {};
console.log(obj.toString());//【object Object】
var arr = new Array(); console.log(arr.toString());//這裏的結果還是【object Object】,這裏有個問題需要注意,內置對象重寫了toString方法 //我們用回調的方法 console.log(Object.prototype.toString.call(arr));//[object Array] var d = new Date(); console.log(Object.prototype.toString.call(d));//[object Date] //測試函數 function classof(obj) { if(obj === null) { return 'Null'; } if(obj === undefined) { return 'Undefined'; } return Object.prototype.toString.call(obj).slice(8, -1); } var x = null;//Null x = undefined;//Undefined x = 123;//Number x = 12.3;//Number x = 'foodoir';//String x = true;//Boolean x = [];//Array x = window;//glable x = function() {};//Function function f() {}; x = new f();//Object console.log(classof(x));
對象的擴展標記(extensible flag)指明瞭(在ECMAScript5中)是否可以向該對象添加新屬性
所有內置對象和自定義對象都是顯示可擴展的,宿主對象的可擴展性由JavaScript引擎定義的。
可以通過Object.preventExtensions()將對象設置為不可擴展的,而且不能再轉換成可擴展的了,可以通過Object.isExtensible()檢測對象是否是可擴展的。示例代碼如下:
preventExtensions()隻影響到對象本身的可擴展性,如果給一個不可擴展的對象的原型添加屬性,這個不可擴展的對象同樣會繼承這些新屬性
可擴展性的目的是將對象鎖定,防止外接干擾,通常和對象的屬性的可配置行與可寫性配合使用
var obj = {};
//檢測對象是否可擴展
console.log(Object.isExtensible(obj));//true var d = new Date(); console.log(Object.isExtensible(d));//true obj.x = 1; console.log(obj.x);//1 //通過preventExtensions()將對象變為不可擴展的 obj1 = Object.preventExtensions(obj); console.log(obj === obj1);//true console.log(Object.isExtensible(obj1));//false obj1.y = 2; console.log(obj1.y);//undefined //通過下面的這種方法會報錯 Object.defineProperty(obj1, 'z', { value: 1 });
問題:如何把對象變為不可擴展的,且保持對象自身的屬性變為不可修改的?
Object.seal()和Object.preventExtensions()類似,除了能夠將對象設置為不可擴展的,還可以將對象的所有自身屬性都設置為不可配置的。也就是説不能給這個對象添加新屬性,而且它已有的屬性也不能刪除或配置,不過它已有的可寫屬性依然可以設置。可以通過Object.isSealed()檢測對象是否封閉
var obj = {
x: 1,
y: 2, username: 'foodoir' }; obj.age = 12; delete obj.x; var o = Object.seal(obj); console.log(obj === o);//true console.log(Object.isExtensible(o));//false,不可擴展的 obj.y = 55;//嘗試修改 console.log(obj.y);//此時仍然可以修改 /*訪問器屬性是不可以修改的,運行下面代碼會報錯 Object.defineProperty(obj,'username',{ get :function(){ return 'this is a test'; } }); */ o.z = 77; console.log(o.z); console.log(o.username);//foodoir delete o.username; console.log(o.username);//foodoir // Object.defineProperties(obj,'username',{value:'hello'});//同樣會報錯 console.log(Object.isSealed(o));//true,被封閉了 console.log(o.username);//foodoir console.log(Object.getOwnPropertyDescriptor(obj, 'username')); //Object configurable: falseenumerable: true value: "king" writable: true __proto__: Object
Object.freeze()將更嚴格地鎖定對象--凍結(frozen).除了對象設置為不可擴展的和將其屬性設置為不可配置的之外,還可以將它自身的所有數據屬性設置為只讀(如果對象的存儲器屬性具有setter方法,存取器屬性將不受影響,仍可以通過給屬性賦值調用它們)。可以使用Object.isFroze()來檢測對象是否凍結。
var obj = {
prop: function() {},
foo: 'foodoir' }; obj.test = 'this is a test'; delete obj.prop; var o = Object.freeze(obj); console.log(obj === o);//true console.log(Object.isFrozen(o));//true,已經被凍結 //凍結後所有的操作都會失敗 o.x = 1; console.log(o.x);//undefined console.log(o.foo);//foodoir o.foo = 'hello'; console.log(o.foo);//foodoir,在嚴格模式下會報出異常 //淺凍結 var obj1 = { internal: {} }; //凍結obj1 Object.freeze(obj1); //在屬性中添加值1 obj1.internal.x = 1; console.log(obj1.internal.x);//1 //遞歸的凍結(深度凍結) function deepFreeze(obj) { var prop, propKey; Object.freeze(obj); for(propKey in obj) { //如果還是一個對象的話,將obj[propKey]賦值給prop prop = obj[propKey]; //如果沒有自己的屬性或者不是一個Object或者凍結這個函數 if(!obj.hasOwnProperty(propKey) || !(typeof prop === 'object') || Object.isFrozen(prop)) { continue; } deepFreeze(prop); } } var obj2 = { internal: {} }; deepFreeze(obj2); obj2.internal.x = 1; console.log(obj2.internal.x);//undefined
下面是關於可擴展性的一些小結,直接看代碼
//默認對象是可擴展的,也就是非凍結的
console.log(Object.isFrozen({}));//false
//一個不可擴展的對象同時也是一個凍結的對象 var obj=Object.preventExtensions({}); console.log(Object.isFrozen(obj));//true //一個非空對象默認也是非凍結 var obj1={x:1}; console.log(Object.isFrozen(obj1));//false Object.preventExtensions(obj1);//將對象變成不可擴展的 console.log(Object.isFrozen(obj1));//false delete obj1.x;//刪掉屬性之後,變成可凍結的了 console.log(Object.isFrozen(obj1));//true //一個不可擴展的對象,但是擁有一個可寫但不可配置的屬性,仍然是非凍結的 var obj2={x:1}; Object.preventExtensions(obj2); Object.defineProperty(obj2,'x',{writable:false}); console.log(Object.isFrozen(obj2));//false,非凍結 //將x變為不可配置 Object.defineProperty(obj2,'x',{configurable:false}); console.log(Object.isFrozen(obj2));//true,凍結了 //如果一個不可擴展的對象,擁有一個不可配置但可寫的屬性,是非凍結的 var obj3={x:1}; Object.preventExtensions(obj3); Object.defineProperty(obj3,'x',{configurable:false}); console.log(Object.isFrozen(obj3));//false,非凍結 //將x變為不可寫 Object.defineProperty(obj3,'x',{writable:false}); console.log(Object.isFrozen(obj3));//true,凍結了 //如果一個不可擴展的對象擁有一個訪問器屬性,它也是非凍結的 var obj4={ get test(){ return 1; } }; Object.preventExtensions(obj4); console.log(Object.isFrozen(obj4));//false,非凍結的 Object.defineProperty(obj4,'test',{configurable:false}); console.log(Object.isFrozen(obj4));//true,凍結的 //直接凍結一個對象 var obj5={x:1}; Object.freeze(obj5); console.log(Object.isFrozen(obj5));//true,被凍結了 console.log(Object.isSealed(obj5));//true,密封的 console.log(Object.isExtensible(obj5));//true,不可擴展的
到這裏,我們再來小結一下Object.prototype和Object對象有哪些屬性和方法。
Object對象的屬性
Object.prototype: 可以為所有Object類型的對象添加屬性
Object對象的方法
Object.create(): 指定原型對象和屬性創建一個對象。
語法
Object.create(proto, [propertiesObject])
參數
proto: 一個對象, 作為新創建對象的原型
propertiesObject: 一個對象值, 可以包含若干個屬性, 屬性名稱為新建對象的屬性名, 屬性值為那個屬性的屬性描述對象。
Object.defineProperty(): 給對象添加 / 修改一個屬性並指定該屬性的配置
語法
Object.defineProperty(obj, prop, descriptor)
參數
obj: 需要定義的對象
prop: 需要定義或修改的屬性名
descriptor: 屬性定義或修改的屬性的描述
描述
該方法允許精確添加或修改對象的屬性。 正常的屬性添加通過賦值來創建並顯示在屬性枚舉中(
for...in 循環 或 Object.keys 方法), 這種方式添加的屬性值可能被改變, 也可能會被 刪除。 該方法允許改變這些額外細節的默認設置。
對象裏目前存在的屬性描述符有兩種主要形式: 數據描述符和存取描述符。 數據描述符是一個擁有可寫或不可寫值的屬性。 存取描述符是由一對 getter - setter 函數功能來描述的屬性。 描述符必須是兩種形式之一;
數據描述符和存取描述符均具有以下可選鍵值
configureable: 當且僅當這個屬性描述符值為 true 時, 該屬性可能會改變, 也可能會被從相應的對象刪除。 默認為 false。
enumerable: true 當且僅當該屬性出現在相應的對象枚舉屬性中。 默認為 false。
value: 與屬性有關的值。 可以是任何有效的Javascript值。 默認為undefined
writable: true當且僅當可能用賦值運算符改變與屬性相關的值。 默認為false
存取描述同時具有以下可選鍵值
get: 一個給屬性提供getter的方法, 如果沒有getter則為undefined。 方法將返回作用屬性的值, 默認為undefined
set: 一個給屬性提供setter的方法, 如果沒有setter則為undefined。 該方法將受到作為唯一參數的新值分配給屬性。 默認為undefined
注意
這些選項不一定是自身屬性, 如果是繼承來的也要考慮。 為了確認保留這些默認值, 你可能要在這之前凍結 Object.prototype, 明確指定所有的選項, 或者將 __proto__ 屬性指向空。
Object.defineProperties(): 在一個對象上添加或修改一個或者多個自有屬性, 並返回該對象。
語法
Object.defineProperities(obj, props)
參數
obj: 將要被添加屬性或修改屬性的對象
props: 該對象的一個或多個鍵值對定義了將要為對象添加或修改的屬性的具體配置
Object.keys(): 方法會返回一個由給定對象的所有可枚舉自身屬性的屬性名組成的數組, 數組中屬性名的排列順序和使用for - in循環遍歷該對象時返回的順序一致(兩者的主要區別是for - in還會遍歷除一個對象從其原型鏈上繼承到得可枚舉的屬性)
語法
Object.keys(obj)
參數
返回該對象的所有可枚舉自身屬性的屬性名
描述
Object.keys 返回一個所有元素為字符串的數組, 其元素來自於從給定的對象上面可直接枚舉的屬性。 這些屬性的順序與手動遍歷該對象屬性時的一致。
如果你想獲取一個對象的所有屬性, ,甚至包括不可枚舉的, 可以通過Object.getOwnPropertyNames() 實現
Object.getOwnPropertyNames(): 返回一個由指定對象的所有自身屬性的屬性名( 包括不可枚舉屬性) 組成的數組
語法
Object.getOwnPropertyNames(obj)
參數
obj: 要查看的對象
描述
Object.getOwnPropertyNames 返回一個數組, 該數組對元素是 obj 自身擁有的枚舉或不可枚舉屬性名稱字符串
數組中枚舉屬性的順序與通過
for...in loop( 或 Object.keys)) 迭代該對象屬性時一致。 數組中不可枚舉屬性的順序未定義。
Object.getOwnPropertyDescriptor(): 返回指定對象上一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該對象的屬性, 不需要從原型鏈上進行查找的屬性))
語法
Object.getOwnPropertyDescriptor(obj, prop)
參數
obj: 在該對象上查看屬性
prop: 一個屬性名稱, 該屬性的屬性描述被返回
返回值
如果指定的屬性存在於對象上, 則返回其屬性描述符( property descriptor), 否則返回 undefined
描述
該方法允許對一個屬性的描述進行檢索。 在 Javascript 中, 屬性 由一個字符串類型的“ 名字”( name) 和一個“ 屬性描述符”( property descriptor) 對象構成一個屬性描述符是一個記錄
Object.getPrototypeOf(): 返回指定對象的原型(也就是該對象內部屬性[[Prototype]] 的值)
語法
Object.getPrototypeOf(obj)
參數
要返回的對象
描述
如果參數不是一個對象類型, 將跑出TypeError異常
Object.freeze(): 凍結一個對象。 凍結對象是指那些不能添加新的屬性, 不能修改已有屬性的值, 不能刪除已有屬性, 以及不能修改已有屬性的可枚舉性、 可配置性、 可寫性的對象。 也就是説這個對象永遠不能改變的。
語法
Object.freeze(obj)
參數
obj: 要被凍結的對象
描述
凍結對象的所有自身屬性都不可能以任何方式被修改。 任何嘗試修改該對象的操作都會失敗, 可能是靜默失敗, 也可能會拋出異常( 嚴格模式中)
數據屬性的值不可更改, 訪問器屬性( 有getter和setter) 也同樣( 但由於是函數調用, 給人的錯覺是還是可以修改這個屬性)。 如果一個屬性的值是個對象, 則這個對象中的屬性是可以修改的, 除非它也是個凍結對象。
Object.isFrozen(): 判斷對象是否已經被凍結
語法
Object.isFrozen(obj)
參數
obj: 被檢測的對象
描述
一個對象是凍結的( frozen) 是指它不可擴展, 所有屬性都是不可配置的( non - configurable), 且所有數據屬性( data properties) 都是不可寫的( non - writable)數據屬性是值那些沒有取值器( getter) 或賦值器( setter) 的屬性。
Object.preventExtensions(): 阻止對象擴展
語法
Object.preventExtensions(obj)
參數
obj: 將要變得不可擴展的對象
描述
如果一個對象可以添加新的屬性, 則這個對象是可擴展的。 preventExtensions 可以讓這個對象變的不可擴展, 也就是不能再有新的屬性。
需要注意的是不可擴展的對象的屬性通常仍然可以被刪除
嘗試給一個不可擴展對象添加新屬性的操作將會失敗, 不過可能是靜默失敗, 也可能會拋出 TypeError 異常( 嚴格模式)。
Object.isExtensible(): 檢測一個對象是否可擴展(是否可以在它上面添加新的屬性)
語法
Object.isExtensible(obj)
參數
obj: 需要檢測的對象
描述
默認情況下, 對象是可擴展的: 即可以為他們添加新的屬性。 以及它們的 __proto__ 屬性可以被更改。
Object.preventExtensions, Object.seal 或 Object.freeze 方法都可以標記一個對象為不可擴展( non - extensible)。
Object.seal(): 可以讓一個對象密封, 並返回被密封之後的對象。 密封對象是指那些不能添加新的屬性、 不能刪除已有屬性, 以及不能修改已有屬性的可枚舉性、 可配置性、 可寫性, 但可能可以修改已有屬性的值的對象
語法
Object.seal(obj)
參數
obj: 要被密封的對象
描述
通常情況下, 一個對象是可擴展的( 可以添加新的屬性)
密封一個對象會讓這個對象變的不能添加新屬性, 且所有已有屬性會變的不可配置。
屬性不可配置的效果就是屬性變的不可刪除, 以及一個數據屬性不能被重新定義成為訪問器屬性, 或者反之。
但屬性的值仍然可以修改。 嘗試刪除一個密封對象的屬性或者將某個密封對象的屬性從數據屬性轉換成訪問器屬性, 結果會靜默失敗或拋出TypeError 異常( 嚴格模式)。
不會影響從原型鏈上繼承的屬性。 但 __proto__() 屬性的值也會不能修改。
Object.isSealed(): 檢測一個對象是否被密封sealed
語法
Object.isSealed(obj)
參數
obj: 要被檢測的對象
描述
如果這個對象是密封的, 則返回 true, 否則返回 false。
密封對象是指那些不可 擴展 的, 且所有自身屬性都不可配置的( non - configurable) 對象。
Object.prototype的屬性
Object.prototype.constructor: 返回一個指向創建了該對象原型的函數引用
注意:該屬性的值是那個函數本身, 而不是一個包含函數名稱的字符串。 對於原始值( 如1, true 或 "test"),該屬性為只讀。所有對象都會從它的原型上繼承一個 constructor 屬性
Object.prototype的方法
Object.prototype.hasOwnProperty(): 檢測某個對象是否含有指定的自身屬性
語法
obj.hasOwnProperty(prop)
參數
要檢測的屬性名稱
描述
所有繼承了 Object.prototype 的對象都會從原型鏈上繼承到 hasOwnProperty 方法
這個方法可以用來檢測一個對象是否含有特定的自身屬性, 和 in 運算符不同, 該方法會忽略掉那些從原型鏈上繼承到的屬性
Object.prototype.isPrototypeOf(): 檢測一個對象是否存在於另一個對象的原型鏈上
語法
prototype.isPrototypeOf(object)
參數
prototype: 檢測該對象是否在參數object的原型鏈上
object: 在該對象的原型鏈上搜尋
描述
isPrototypeOf方法允許你檢測一個對象是否存在於另一個對象的原型鏈上
Object.prototype.propertyIsEnumerable(): 檢測指定的屬性名是否是當前對象可枚舉的自身屬性
語法
obj.propertyIsEnumerable(prop)
參數
prop: 需要檢測的屬性名
描述
每個對象都有 propertyIsEnumerable 方法。 該方法可以判斷出指定的屬性是否是自身的可枚舉屬性, 也就是説該屬性是否可以通過for...in 循環等遍歷到
有些屬性雖然可以通過for...in 循環遍歷到, 但因為它們不是自身屬性, 而是從原型鏈上繼承的屬性, 所以該方法也會返回false。 如果對象沒有指定的屬性, 該方法返回 false。
Object.prototype.toString(): 返回一個代表該對象的字符串
語法
object.toString()
描述
當對象需要轉換為字符串時, 會調用它的toString() 方法.
默認情況下, 每個對象都會從Object上繼承到toString() 方法, 如果這個方法沒有被這個對象自身或者更接近的上層原型上的同名方法覆蓋(遮蔽), 則調用該對象的toString() 方法時會返回 "[object type]",
這裏的字符串type表示了一個對象類型
Object.prototype.valueOf(): 返回的是this值, 即對象本身
語法
object.valueOf()
返回值
在其他類型的對象中, valueOf有可能返回一個不同的值