一. 對(duì)象基本操作
1. 對(duì)象基礎(chǔ)
一個(gè)對(duì)象由許多成員組成,每一個(gè)成員都有一個(gè)名字和一個(gè)值。每一個(gè)名字/值對(duì)被逗號(hào)(,)分隔開,并且名字和值之間由冒號(hào)(:)分隔。
2. 創(chuàng)建對(duì)象
2.1 創(chuàng)建對(duì)象常見的方式有以下六種:
- new 操作符 + Object 創(chuàng)建對(duì)象
var person = new Object();
- 字面式創(chuàng)建對(duì)象
var person ={
name: "sidashen"
}
- 工廠模式
function createPerson(name,age,family) {
var o = new Object();
o.name = name;
o.age = age;
o.family = family;
o.say = function(){
alert(this.name);
}
return o;
}
var person1 = createPerson("sidashen",21,["lida","lier","wangwu"]);
var person2 = createPerson("shaonianyingxiong",18,["lida","lier","lisi"]);
- 構(gòu)造函數(shù)模式
function Person(name,age,family) {
this.name = name;
this.age = age;
this.family = family;
this.say = function(){
alert(this.name);
}
}
var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
var person2 = new Person("lisi",21,["lida","lier","lisi"]);
- 原型模式
function Person() {
}
Person.prototype.name = "lisi";
Person.prototype.age = 21;
Person.prototype.family = ["lida","lier","wangwu"];
Person.prototype.say = function(){
alert(this.name);
};
console.log(Person.prototype);
var person1 = new Person();
console.log(person1.name);
- 混合模式(構(gòu)造函數(shù)模式+原型模式)
function Person(name,age,family){
this.name = name;
this.age = age;
this.family = family;
}
Person.prototype = {
constructor: Person,
say: function(){
alert(this.name);
}
}
var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
console.log(person1);
var person2 = new Person("wangwu",21,["lida","lier","lisi"]);
console.log(person2);
2.2 創(chuàng)建一個(gè)對(duì)象
var obj = {};
3. 向?qū)ο笾刑砑訉傩?/h4>
語法:對(duì)象.屬性名 = 屬性值;
3.1 向obj中添加name屬性
obj.name = "sidashen";
console.log(obj); // object { name = "sidashen"}
3.2 向obj中添加gender屬性
obj.gender = "female";
console.log(obj); // object { gender = "female"}
4. 讀取對(duì)象中的屬性
語法:對(duì)象.屬性名
obj.name; // "sidashen"
注意:如果讀取對(duì)象中沒有的屬性不會(huì)報(bào)錯(cuò),而是返回"undefined".
5. 修改屬性值
語法:對(duì)象.屬性名 = 新值
obj.name = "shaonianyingxiong";
console.log(obj); // object { name = "shaonianyingxiong"}
5. 刪除對(duì)象的屬性
語法:delete 對(duì)象.屬性名
delete obj.name; //刪除name屬性
console.log(obj.name); // "undefined"
二. 屬性名和屬性值
1. 屬性名
1.1 對(duì)象的屬性名不強(qiáng)制要求遵守標(biāo)識(shí)符規(guī)范。
如:obj.var = "hello"; 也可以命名,但不推薦。
1.2 如果使用特殊屬性名,如@#$afj.,不能采用點(diǎn)表示法。
1.3 括號(hào)表示法。
語法:對(duì)象["屬性名"] = 屬性值
obj["123"] = 789;
console.log(obj["123"]); // 讀取時(shí)也需要采用這種表示法
括號(hào)表示法更靈活,在[]中可以直接傳遞一個(gè)變量,這樣變量值是多少就讀取那個(gè)屬性。
var n = "123";
console.log(obj[n]); // "789"
var n = "nihao";
console.log(obj[n]); // "nihao"
2. 屬性值
JavaScript對(duì)象的屬性值,可以是任意的數(shù)據(jù)類型,如字符串,數(shù)組,函數(shù),甚至可以是一個(gè)對(duì)象。
var obj2 = new Object();
obj2.name = "周杰倫";
// 將obj2設(shè)置為obj的屬性
obj.test = obj2; // test的屬性值為obj2
console.log(obj.test); // name = "周杰倫"
console.log(obj.test.name); // "周杰倫"
3. in運(yùn)算符
通過該運(yùn)算符,可檢查一個(gè)對(duì)象中是否含有指定屬性,有則返回true,沒有返回false。
語法:"屬性名" in 對(duì)象
檢查obj中是否有test2的屬性
console.log("test2" in obj); // false
console.log("test" in obj); // true
三. 基本數(shù)據(jù)類型和引用數(shù)據(jù)類型
1.
基本數(shù)據(jù)類型:String, Number, Boolean, Null, Undefined;
引用數(shù)據(jù)類型:Object.
1.1 基本數(shù)據(jù)類型舉例
var a = 123;
var b = a;
a++;
console.log(a); //124
console.log(b); //123
在這里會(huì)發(fā)現(xiàn),a與b相互獨(dú)立,a的改變并不會(huì)影響b的屬性。
1.2 引用數(shù)據(jù)類型舉例
var obj = new Object();
obj.name = "周杰倫";
console.log(obj.name); // "周杰倫"
var obj2 = obj;
console.log(obj2.name); // "周杰倫"
//修改obj的name屬性
obj.name = "Jay";
console.log(obj2.name); // "Jay"
在這里會(huì)發(fā)現(xiàn),當(dāng)改變obj的屬性值時(shí),obj2的屬性值也跟著發(fā)生改變,這是為什么呢?
2. 基本數(shù)據(jù)類型和引用數(shù)據(jù)類型的區(qū)別
2.1 基本數(shù)據(jù)類型的特點(diǎn)
- JavaScript中的變量都是保存到棧內(nèi)存的;
- 基本數(shù)據(jù)類型的值直接在棧內(nèi)存中存儲(chǔ);
- 值與值之間相互獨(dú)立,修改一個(gè)變量不會(huì)影響其他變量。
2.2 引用數(shù)據(jù)類型的特點(diǎn)
- 引用數(shù)據(jù)類型(對(duì)象)是保存在堆內(nèi)存中的,每創(chuàng)建一個(gè)新對(duì)象,就會(huì)在堆內(nèi)存中開辟一個(gè)新空間,而變量保存的是對(duì)象的內(nèi)存地址(對(duì)象的引用)。如果兩個(gè)變量保存的是同一個(gè)對(duì)象引用,當(dāng)其中一個(gè)通過一個(gè)變量修改屬性時(shí),另一個(gè)也會(huì)受到影響。
- 如果設(shè)置
obj2的屬性值為null:
obj2 = null;
console.log(obj); // object
console.log(obj2); // null
這里不會(huì)像之前一樣同時(shí)變化是因?yàn)椋?dāng)obj2的屬性值為null,相當(dāng)于將obj2里保存的內(nèi)存地址與堆內(nèi)存中對(duì)應(yīng)的空間斷開連接,所以obj不會(huì)被影響。
2.3 基本數(shù)據(jù)類型和引用數(shù)據(jù)類型在比較時(shí)的不同
- 基本數(shù)據(jù)類型
var c = 10;
var d = 10;
console.log(c == d); // "ture"
- 引用數(shù)據(jù)類型
var obj3 = new Object();
var obj4 = new Object();
obj3.name = "Chou";
obj4.name = "Chou";
console.log(obj3 == obj4); // "false"
當(dāng)比較兩個(gè)基本數(shù)據(jù)類型的值時(shí),就是比較值,如 1 ===1;
而比較兩個(gè)引用數(shù)據(jù)類型時(shí),比較的是對(duì)象的內(nèi)存地址,如果兩個(gè)對(duì)象一模一樣,但是地址不同,結(jié)果也返回false。所以在上面例子中,obj3和obj4在堆內(nèi)存中分別開辟了新的內(nèi)存空間,有不一樣的內(nèi)存地址,所以比較相等的結(jié)果為false。
四. 對(duì)象字面量
1. 使用對(duì)象字面量來創(chuàng)建一個(gè)對(duì)象
var obj = {};
console.log(obj); // "object"
obj.name = "周杰倫";
console.log(obj.name); // "周杰倫"
2. 使用對(duì)象字面量,可以在創(chuàng)建對(duì)象時(shí),直接指定對(duì)象中的屬性
語法:{屬性名:屬性值1,屬性值2,...}
var obj2 = {name: "Jay"}
var obj2 = {name: "Jay", age: 40}
console.log(obj2); // name = "Jay", age = "40"
上述代碼可簡(jiǎn)寫同時(shí)也是常用寫法為:
var obj2 = {
name: "Jay",
age: 40
}
console.log(obj2); // name = "Jay", age = "40"
3. 對(duì)象字面量的屬性名建議不加引號(hào)
4. 屬性名和屬性值是一組一組的名值對(duì)結(jié)構(gòu),名和值之間使用冒號(hào)(:)連接,多個(gè)名值對(duì)之間使用逗號(hào)(,)隔開。如果一個(gè)屬性之后沒有其他屬性了,則不寫逗號(hào)(,)。
五. this關(guān)鍵詞
JavaScript函數(shù)中的this指向并不是在函數(shù)定義的時(shí)候確定的,而是在調(diào)用的時(shí)候確定的。換句話說,函數(shù)的調(diào)用方式?jīng)Q定了this指向。
JavaScript中,普通的函數(shù)調(diào)用方式有三種:直接調(diào)用、方法調(diào)用和new調(diào)用。除此之外,還有一些特殊的調(diào)用方式,比如通過bind()將函數(shù)綁定到對(duì)象之后再進(jìn)行調(diào)用、通過call()、apply()進(jìn)行調(diào)用等。而ES6引入了箭頭函數(shù)之后,箭頭函數(shù)調(diào)用時(shí),其this指向又有所不同
1. 直接調(diào)用
直接調(diào)用,就是通過函數(shù)名(...)這種方式調(diào)用。這時(shí)候,函數(shù)內(nèi)部的this指向全局對(duì)象,在瀏覽器中全局對(duì)象是window,在Node中全局對(duì)象是global。
// 簡(jiǎn)單兼容瀏覽器和 NodeJs 的全局對(duì)象
const _global=typeof window==="undefined"?global:window;
function test() {
console.log(this===_global); // true
}
test(); // 直接調(diào)用
這里需要注意的一點(diǎn)是,直接調(diào)用并不是指在全局作用域下進(jìn)行調(diào)用,在任何作用域下,直接通過 函數(shù)名(...) 來對(duì)函數(shù)進(jìn)行調(diào)用的方式,都稱為直接調(diào)用。
(function(_global) {
// 通過 IIFE 限定作用域
function test() {
console.log(this === _global); // true
}
test(); // 非全局作用域下的直接調(diào)用
})(typeof window === "undefined" ? global : window);
2. bind()對(duì)直接調(diào)用的影響
Function.prototype.bind()的作用是將當(dāng)前函數(shù)與指定的對(duì)象綁定,并返回一個(gè)新函數(shù),這個(gè)新函數(shù)無論以什么樣的方式調(diào)用,其this始終指向綁定的對(duì)象。
const obj = {};
function test() {
console.log(this === obj);
}
const testObj = test.bind(obj);
test(); // false
testObj(); // true
那么bind()干了什么?
const obj = {};
function test() {
console.log(this === obj);
}
// 自定義的函數(shù),模擬bind()對(duì)this的影響
function myBind(func, target) {
return function() {
return func.apply(target, arguments);
};
}
const testObj = myBind(test, obj);
test(); // false
testObj(); // true
從上面的示例可以看到,首先,通過閉包,保持了target,即綁定的對(duì)象;然后在調(diào)用函數(shù)的時(shí)候,對(duì)原函數(shù)使用了apply方法來指定函數(shù)的this。當(dāng)然原生的bind()實(shí)現(xiàn)可能會(huì)不同,而且更高效。但這個(gè)示例說明了bind()的可行性。
3. call和apply對(duì)this的影響
Function.prototype.apply()和Function.prototype.call()這兩方法的第一個(gè)參數(shù)都是指定函數(shù)運(yùn)行時(shí)其中的this指向。
不過使用apply和call的時(shí)候仍然需要注意,如果目錄函數(shù)本身是一個(gè)綁定了this對(duì)象的函數(shù),那apply和call不會(huì)像預(yù)期那樣執(zhí)行。
const obj = {};
function test() {
console.log(this === obj);
}
// 綁定到一個(gè)新對(duì)象,而不是 obj
const testObj = test.bind({});
test.apply(obj); // true
// 期望 this 是 obj,即輸出 true
// 但是因?yàn)閠estObj綁定了不是obj的對(duì)象,所以會(huì)輸出false
testObj.apply(obj); // false
4. 方法調(diào)用
方法調(diào)用是指通過對(duì)象來調(diào)用其方法函數(shù),它是對(duì)象.方法函數(shù)(...) 這樣的調(diào)用形式。這種情況下,函數(shù)中的this指向調(diào)用該方法的對(duì)象。但是,同樣需要注意bind()的影響。
const obj = {
// 第一種方式,定義對(duì)象的時(shí)候定義其方法
test() {
console.log(this === obj);
}
};
// 第二種方式,對(duì)象定義好之后為其附加一個(gè)方法(函數(shù)表達(dá)式)
obj.test2 = function() {
console.log(this === obj);
};
// 第三種方式和第二種方式原理相同
// 是對(duì)象定義好之后為其附加一個(gè)方法(函數(shù)定義)
function t() {
console.log(this === obj);
}
obj.test3 = t;
// 這也是為對(duì)象附加一個(gè)方法函數(shù)
// 但是這個(gè)函數(shù)綁定了一個(gè)不是 obj 的其它對(duì)象
obj.test4 = (function() {
console.log(this === obj);
}).bind({});
obj.test(); // true
obj.test2(); // true
obj.test3(); // true
// 受 bind() 影響,test4 中的 this 指向不是 obj
obj.test4(); // false
5. 方法中this指向全局對(duì)象的情況
這里說的是方法中而不是方法調(diào)用中。方法中的this指向全局對(duì)象,如果不是因?yàn)?code>bind(),那就一定是因?yàn)椴皇怯玫姆椒ㄕ{(diào)用方式。
const obj = {
test() {
console.log(this === obj);
}
};
const t = obj.test;
t(); // false
t就是obj的test方法,但是t()調(diào)用時(shí),其中的this指向了全局。
6. new調(diào)用
在ES6之前,每一個(gè)函數(shù)都可以當(dāng)作是構(gòu)造函數(shù),通過new調(diào)用來產(chǎn)生新的對(duì)象(函數(shù)內(nèi)無特定返回值的情況下)。而ES6改變了這種狀態(tài),雖然class定義的類用typeof運(yùn)算符得到的仍然是"function",但它不能像普通函數(shù)一樣直接調(diào)用;同時(shí),class中定義的方法函數(shù),也不能當(dāng)作構(gòu)造函數(shù)用new來調(diào)用。
而在ES5中,用new調(diào)用一個(gè)構(gòu)造函數(shù),會(huì)創(chuàng)建一個(gè)新對(duì)象,而其中的this就指向這個(gè)新對(duì)象。這沒有什么懸念,因?yàn)?code>new本身就是設(shè)計(jì)來創(chuàng)建新對(duì)象的。
var data = "Hi"; // 全局變量
function AClass(data) {
this.data = data;
}
var a = new AClass("Hello World");
console.log(a.data); // Hello World
console.log(data); // Hi
var b = new AClass("Hello World");
console.log(a === b); // false
7. 箭頭函數(shù)中的this
箭頭函數(shù)沒有自己的this綁定。箭頭函數(shù)中使用的this,其實(shí)是直接包含它的那個(gè)函數(shù)或函數(shù)表達(dá)式中的this。
const obj = {
test() {
const arrow = () => {
// 這里的 this 是 test() 中的 this,
// 由 test() 的調(diào)用方式?jīng)Q定
console.log(this === obj);
};
arrow();
},
getArrow() {
return () => {
// 這里的 this 是 getArrow() 中的 this,
// 由 getArrow() 的調(diào)用方式?jīng)Q定
console.log(this === obj);
};
}
};
obj.test(); // true
const arrow = obj.getArrow();
arrow(); // true
示例中的兩個(gè)this都是由箭頭函數(shù)的直接外層函數(shù)(方法)決定的,而方法函數(shù)中的this是由其調(diào)用方式?jīng)Q定的。上例的調(diào)用方式都是方法調(diào)用,所以this都指向方法調(diào)用的對(duì)象,即obj。
箭頭函數(shù)讓大家在使用閉包的時(shí)候不需要太糾結(jié)this,不需要通過像_this這樣的局部變量來臨時(shí)引用this給閉包函數(shù)使用。來看一段Babel對(duì)箭頭函數(shù)的轉(zhuǎn)譯可能能加深理解。
// ES6
const obj = {
getArrow() {
return () => {
console.log(this === obj);
};
}
}
// ES5,由 Babel 轉(zhuǎn)譯
var obj = {
getArrow: function getArrow() {
var _this = this;
return function () {
console.log(_this === obj);
};
}
};
另外需要注意的是,箭頭函數(shù)不能用new調(diào)用,不能bind()到某個(gè)對(duì)象(雖然bind()方法調(diào)用沒問題,但是不會(huì)產(chǎn)生預(yù)期效果)。不管在什么情況下使用箭頭函數(shù),它本身是沒有綁定this的,它用的是直接外層函數(shù)(即包含它的最近的一層函數(shù)或函數(shù)表達(dá)式)綁定的this。