var a = 1;
console.log(typeof a);// 'number'
var b = '1';
console.log(typeof b);// 'string'
var c = true;
console.log(typeof c);// 'boolean'
var d = null;
console.log(typeof d);// 'object'
var e = undefined;
console.log(typeof e);// 'undefined'
函數對象
typeof是用來判斷變量類型
instancaof是用來檢測這個實例是不是由這個類所創建,換言之,就是檢測這個實例對象是不是這個類new出來的。
constructor是每一個實例對象都擁有的屬性,而這個屬性也相當于是一個指針,它指向于創建當前對象的對象。
從下面的代碼可以看出,在javascript中函數即是對象。
var o = new Object();
console.log(typeof o);// 'object'
console.log(o instanceof Object);// true
console.log(o.constructor === Object);// true
console.log(o instanceof Function);// false
console.log(o.constructor === Function);// false
var f = new Function();
console.log(typeof f);// 'function'
console.log(f instanceof Function);// true
console.log(f.constructor === Function);// true
console.log(f instanceof Object);// true
console.log(f.constructor === Object);// false
console.log(Function instanceof Object);// true
console.log(Function.constructor === Object);// false
console.log(Function instanceof Function);// true
console.log(Function.constructor === Function);// true
javascript對象可以任意自定義屬性和方法,沒有c++和java中的class的約束。
var o = {
name:'nexus', // 定義一個屬性
showMsg:function(){ // 定義一個方法
console.log('my name is ' + this.name);
}
};
console.log(o.name);// 'nexus'
o.showMsg();// 'my name is nexus'
例子中新建了一個對象o,其中o對象有屬性name和方法showMsg。
o.name就是'neuxs'。
o.showMsg()就是運行o的showMsg函數,其中調用showMsg函數的宿主是o,所以此時的this是o,this.name也就是'nexus',打印'my name is nexus'。
構造函數
例子中定義了一個函數,返回一個對象,其中包括屬性和方法,屬性name和age通過行參自定義。
function Person(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.showMsg = function(){
console.log(this.name + ':' + this.age);
};
return o;
}
var p = Person('nexus',18);
console.log(p.name);// 'nexus'
console.log(p.age);// 18
p.showMsg();// 'nexus : 18'
函數還可以作為構造函數使用,像上面所述的 Object 和 Array 原生構造函數一樣,在運行時會自動出現在執行環境中:
function Person(name,age){
this.name = name;
this.age = age;
this.showMsg = function(){
console.log(this.name + ':' + this.age);
};
}
var p = new Person('nexus',18);
console.log(p.name);// 'nexus'
console.log(p.age);// 18
console.log(p.showMsg());// 'nexus : 18'
要創建 Person 的新實例,必須使用 new 操作符。
創建一個新對象,
將構造函數的作用域賦給新對象(因此 this 就指向了這個新對象),
執行構造函數中的代碼(為這個新對象添加屬性和方法),
返回新對象。
// 作為普通函數調用,添加到 window
Person('nexus',18);
console.log(window.name);// 'nexus'
console.log(window.age);// 18
window.showMsg();// 'nexus : 18'
// 當作構造函數使用,新建一個對象p
var p = new Person('nexus', 18);
console.log(p.name);// 'nexus'
console.log(p.age);// 18
p.showMsg();// 'nexus : 18'
// 在另一個對象的作用域中調用
var o = {};
Person.call(o,'nexus',18);
console.log(o.name);// 'nexus'
console.log(o.age);// 18
o.showMsg(); // 'nexus : 18'
構造函數雖然簡潔明了,但是存在缺點:
var p1 = new Person('nexus',18);
var p2 = new Person('nexus',18);
console.log(p1.name == p2.name);// true
console.log(p1.age == p2.age);// true
console.log(p1.showMsg == p2.showMsg);// false
我們可以發現,當每次建立一個新對象的時候,每個 Person 實例都包含一個不同的 showMsg() 方法,即使它們的內容相同,也需要另外開辟內存來保存。
創建兩個完成同樣任務的 showMsg 方法沒有必要,這個時候就需要使用prototype。
prototype 原型
我們創建的每個函數都有一個 prototype(原型)屬性。使用原型的好處是可以讓所有對象實例共享它所包含的屬性和方法。換句話說,不必在構造函數中定義對象實例的信息,而是可以將這些信息直接添加到原型中。
function Person(){}
Person.prototype.name = 'nexus';
Person.prototype.age = 18;
Person.prototype.showMsg = function(){
console.log(this.name + ':' + this.age);
};
var p1 = new Person();
p1.showMsg();// 'neuxs : 18'
var p2 = new Person();
p2.showMsg();// 'neuxs : 18'
console.log(p1.showMsg == p2.showMsg);// true
showMsg() 方法和所有屬性直接添加到了 Person 的 prototype 屬性中,構造函數變成了空函數。即使如此,也仍然可以通過調用構造函數來創建新對象,而且新對象還會具有相同的屬性和方法。但與前面的例子不同的是,新對象的這些屬性和方法是由所有實例共享的。換句話說,p1 和 p2 訪問的都是同一組屬性和同一個 showMsg() 函數。
function Person(){}
Person.prototype.name = 'nexus';
Person.prototype.age = 18;
Person.prototype.showMsg = function(){
console.log(this.name + ':' + this.age);
};
var p1 = new Person();
console.log(p1.name);// 'nexus',來自原型
p1.name = "noa";
console.log(p1.name);// 'noa',來自實例
delete p1.name;
console.log(p1.name);// 'nexus',來自原型
delete p1.name;
console.log(p1.name);// 'nexus',來自原型,delete不能刪除原型屬性
當訪問 p1.name 時,需要讀取它的值,因此就會在這個實例上搜索一個名為 name 的屬性。這個屬性確實存在,于是就返回它的值而不必再搜索原型了
使用 delete 操作符刪除了 p1.name,之前它保存的 "noa" 值屏蔽了同名的原型屬性。把它刪除以后,就恢復了對原型中 name 屬性的連接。
從代碼中看出 delete 操作符只能刪除對象的實例name屬性,無法刪除原型的 name 屬性。
function Person(){}
Person.prototype.name = 'nexus';
Person.prototype.age = 18;
Person.prototype.showMsg = function(){
console.log(this.name + ':' + this.age);
};
var p = new Person();
console.log(p instanceof Object);? ? ? // true
console.log(p.constructor === Object);? // false
console.log(p instanceof Person);? ? ? // true
console.log(p.constructor === Person);? // true
重寫整個原型對象 Person.prototype
function Person(){}
Person.prototype = {
name : 'nexus',
age : 18,
showMsg : function () {
console.log(this.name + ':' + this.age);
}
};
Person.prototype.name = 'nexus';
Person.prototype.age = 18;
Person.prototype.showMsg = function(){
console.log(this.name + ':' + this.age);
};
var p = new Person();
console.log(p instanceof Object);? ? ? // true
console.log(p.constructor === Object);? // true
console.log(p instanceof Person);? ? ? // true
console.log(p.constructor === Person);? // false
Person.prototype 重新設置為一個新對象。實例 p.constructor 屬性不再指向 Person 了,而是指向 Object 構造函數。
如果 constructor 的值真的很重要,可以像下面設置。
function Person(){}
Person.prototype = {
constructor : Person,
name : 'nexus',
age : 18,
showMsg : function () {
console.log(this.name + ':' + this.age);
}
};
Person.prototype.name = 'nexus';
Person.prototype.age = 18;
Person.prototype.showMsg = function(){
console.log(this.name + ':' + this.age);
};
var p = new Person();
console.log(p instanceof Object);? ? ? // true
console.log(p.constructor === Object);? // false
console.log(p instanceof Person);? ? ? // true
console.log(p.constructor === Person);? // true
動態性原型
我們對原型對象所做的任何修改都能夠立即反映出來:
function Person(){}
var p = new Person();
Person.prototype.sayHi = function(){
console.log('hi');
};
p.sayHi();? // 'hi'
調用 p.sayHi() 時,首先會在實例中搜索名為 sayHi 的屬性,在沒找到的情況下,會繼續搜索原型。因為實例與原型之間的連接只不過是一個指針,而非一個副本
如果是重寫整個原型對象,那么情況就不一樣了。
通過 p.name 沒有發生改變可以得出結論:調用構造函數時會為實例添加一個指向最初原型的 Prototype 指針,而把原型修改為另外一個對象就等于切斷了構造函數與最初原型之間的聯系。
function Person(){}
var p = new Person();
Person.prototype.name = 'nexus';
Person.prototype = {
constructor: Person,
name:'noa',
sayHi : function () {
console.log('hi');
}
};
console.log(p.name);// 'nexus'
p.sayName();// Uncaught TypeError: p.sayName is not a function
原生對象的原型
我們同樣可以給原生對象添加方法,這里展示一下如何實現一個Array.prototype.map函數的原型方法。
if (!Array.prototype.map) {
Array.prototype.map = function (callback, thisArg) {
// 調用宿主是 null 或者 undefined,則拋出 TypeError 異常
if (this == null) {
throw new TypeError('Array.prototype.map called on null or undefined');
}
// 如果 callback 不是函數,則拋出 TypeError 異常
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 將 O 賦值為調用 map 方法的數組
var O = Object(this);
// 將 len 賦值為數組 O 的長度
var len = O.length >>> 0;
// 如果參數 thisArg 有值,則將 T 賦值為 thisArg ,否則T為 undefined
var T;
if (thisArg) {
T = thisArg;
}
// 創建新數組 A,長度為原數組 O 長度 len
var A = new Array(len);
// 將 k 賦值為0,遍歷開頭
var k = 0;
// 當 k < len 時,執行循環
while (k < len) {
var kValue, mappedValue;
// 遍歷 O,k 為原數組索引
// 那些從來沒被賦過值或者使用 delete 刪除的索引則不會被調用。
if (k in O) {
// kValue 為索引 k 對應的值
kValue = O[k];
// 執行callback,this 指向 T,參數有三個,分別是 kValue:值,k :索引,O :原數組
mappedValue = callback.call(T, kValue, k, O);
// 返回值添加到新數組A中.
A[k] = mappedValue;
}
// k自增1
k++;
}
// 返回新數組A
return A;
};
}
構造函數和原型結合
構造函數用于定義實例屬性,而原型用于定義方法和共享的屬性。
function Person(name, age){
this.name = name;
this.age = age;
this.friends = ['a', 'b'];
}
Person.prototype = {
constructor : Person,
sayName : function(){
console.log(this.name);
}
}
var p1 = new Person('nexus', 18);
var p2 = new Person('noa', 19);
p1.friends.push('c');
console.log(p1.friends);// ['a','b','c']
console.log(p2.friends);// ['a','b']
console.log(p1.friends === p2.friends);// false
console.log(p1.sayName === p2.sayName);// true