基本數據類型html
Stringgit
Number程序員
Booleangithub
null面試
特殊: typeof null === 'Object' //true
segmentfault
Undefinedapi
Symbol 符號(ES6新增)數組
引用數據類型瀏覽器
基本數據類型和引用數據類型的區別(存儲位置不一樣)bash
stack
)中的簡單數據段,佔據空間小、大小固定,屬於被頻繁使用數據,因此放入棧中存儲;heap
)中的對象,佔據空間大、大小不固定,若是存儲在棧中,將會影響程序運行的性能;引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中得到實體數據封裝類對象:Object
、Array
、Boolean
、Number
和 String
其餘對象:Function
、Arguments
、Math
、Date
、RegExp
、Error
typeof null === 'Object' //true
null
是惟一一個用typeof
檢測會返回object
的基本類型值(注意‘基本’兩字)
緣由:不一樣的對象在底層都表示爲二進制 在JavaScript中二進制前三位爲0的話都會被判斷爲object類型 null的二進制表示全是0,天然前三位也是0 因此 typeof null === 「object」
參考連接:深刻理解JS的類型、值、類型轉換
判斷對象用 instanceof,其內部機制是經過原型鏈來判斷的
instanceof原理:判斷實例對象的__proto__
屬性,和構造函數的prototype
屬性,是否爲同一個引用(是否指向同一個地址)
注意1:雖說,實例是由構造函數 new 出來的,可是實例的__proto__
屬性引用的是構造函數的prototype
。也就是說,實例的__proto__
屬性與構造函數自己無關。
注意2:在原型鏈上,原型的上面可能還會有原型,以此類推往上走,繼續找__proto__
屬性。這條鏈上若是能找到, instanceof 的返回結果也是 true。
咱們也能夠試着實現一下 instanceof
function instanceof(left, right) {
// 得到類型的原型
let prototype = right.prototype
// 得到對象的原型
left = left.__proto__
// 判斷對象的類型是否等於類型的原型
while (true) {
if (left === null)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
複製代碼
問題:已知A繼承了B,B繼承了C。怎麼判斷 a 是由A直接生成的實例,仍是B直接生成的實例呢?仍是C直接生成的實例呢?
分析:這就要用到原型的constructor
屬性了。
foo.__proto__.constructor === Foo
的結果爲true,可是 foo.__proto__.constructor === Object
的結果爲false。因此,用 consturctor判斷就比用 instanceof判斷,更爲嚴謹。
Object.prototype.toString.call(xx)
轉換成字符串 String()
toString() 能夠被顯式調用,或者在須要字符串化時自動調用
null 轉換爲 "null",undefined 轉換爲 "undefined",true 轉換爲 "true"。 數字的字符串化則遵循通用規則 極小和極大的 數字使用指數形式:
// 1.07 連續乘以七個 1000
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
// 七個1000一共21位數字
a.toString(); // "1.07e21"
複製代碼
數組的默認 toString() 方法通過了從新定義,將全部單元字符串化之後再用 "," 鏈接起來
var a = [1,2,3];
a.toString(); // "1,2,3"
複製代碼
轉換成數字 Number()
其中 true 轉換爲 1,false 轉換爲 0。undefined 轉換爲 NaN,null 轉換爲 0。 處理失敗 時返回 NaN(處理數字常量失敗時會產生語法錯誤)
轉換成布爾值 Boolean()
• undefined
• null
• false
• +0、-0 和 NaN
• ""
複製代碼
除了上面之外的,都爲true
字符串和數字之間的隱式轉換
一個坑
[] + {}; // "[object Object]" {} + []; // 0
console.log([] + {}); //[object Object]
console.log({} + []); //[object Object]
第一行代碼中,{} 出如今 + 運算符表達式中,所以它被看成一個值(空對象)來處理。 第二行代碼中,{} 被看成一個獨立的空代碼塊(不執行任何操做)。代碼塊結尾不須要分號,因此這裏不存在語法上的問題。最後 + [] 將 [] 顯式強制類型轉換(參見第 4 章) 爲 0。
第四行代碼中,{} 其實應該當成一個代碼塊,而不是一個 Object,當你在console.log使用的時候,{} 被當成了一個 Object
隱式強制類型轉換爲布爾值
下面的狀況會發生 布爾值隱式強制類型轉換。
|| 與 &&
== 與 ===
== 容許在相等比較中進行強制類型轉換,而 === 不容許
[] == ![] //true
複製代碼
參考連接:爲何[] ==![]
4種方式,每種方式的不一樣在於this的初始化
通常而言,在Javascript中,this指向函數執行時的當前對象。
function myFunction(a, b) {
return a * b;
}
myFunction(10, 2); // myFunction(10, 2) 返回 20
window.myFunction(10, 2); //myFunction() 和 window.myFunction() 是同樣的
複製代碼
函數做爲全局對象調用,會使 this 的值成爲全局對象。 使用 window 對象做爲一個變量容易形成程序崩潰。
var myObject = {
firstName:"John",
lastName: "Doe",
fullName: function () {
return this.firstName + " " + this.lastName;
}
}
myObject.fullName(); // 返回 "John Doe"
複製代碼
fullName 方法是一個函數。函數屬於對象。 myObject 是函數的全部者。this對象,擁有 JavaScript 代碼。實例中 this 的值爲 myObject 對象。
函數做爲對象方法調用,會使得 this 的值成爲對象自己。
// 構造函數:
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
// This creates a new object
var x = new myFunction("John","Doe");
x.firstName; // 返回 "John"
複製代碼
構造函數中 this 關鍵字沒有任何的值。 this 的值在函數調用實例化對象(new object)時建立。
call() 和 apply() 是預約義的函數方法。 兩個方法可用於調用函數,兩個方法的第一個參數必須是對象自己。
function myFunction(a, b) {
return a * b;
}
myObject = myFunction.call(myObject, 10, 2); // 返回 20
複製代碼
function myFunction(a, b) {
return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray); // 返回 20
複製代碼
經過 call() 或 apply() 方法你能夠設置 this 的值, 且做爲已存在對象的新方法調用。
在 JavaScript 嚴格模式(strict mode)下, 在調用函數時第一個參數會成爲 this 的值, 即便該參數不是一個對象。
在 JavaScript 非嚴格模式(non-strict mode)下, 若是第一個參數的值是 null 或 undefined, 它將使用全局對象替代。
參考連接:詳解call、apply、bind
做用域就是變量與函數的可訪問範圍,即做用域控制着變量與函數的可見性和生命週期
內層函數可訪問外層函數局部變量
外層函數不能訪問內層函數局部變量
通俗地講,當聲明一個函數時,局部做用域一級一級向上包起來,就是做用域鏈。
1.當執行函數時,老是先從函數內部找尋局部變量
2.若是內部找不到(函數的局部做用域沒有),則會向建立函數的做用域(聲明函數的做用域)尋找,依次向上
閉包就是可以讀取其餘函數內部變量的函數。
函數 A
內部有一個函數 B
,函數 B
能夠訪問到函數 A
中的變量,那麼函數 B
就是閉包。
經典面試題,循環中使用閉包解決
var
定義函數的問題
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
//咱們但願輸出的是1,2,3,4,5,但由於 setTimeout 是個異步函數,因此會先把循環所有執行完畢,這時候 i就是 6 了,因此會輸出五個6
複製代碼
解決方法:
閉包
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
複製代碼
使用 setTimeout
的第三個參數,這個參數會被當成 timer
函數的參數傳入
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j)
},
i * 1000,
i
)
}
複製代碼
使用let
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
複製代碼
對象字面量
person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
複製代碼
工廠方式(內置對象)
var wcDog =new Object();
wcDog.name="旺財";
wcDog.age=3;
wcDog.work=function(){
alert("我是"+wcDog.name+",汪汪汪......");
}
wcDog.work();
複製代碼
方式1與方式2效果同樣,第一種寫法,person
會指向Object
經過構造函數
// 無參
function Person(){}
var person=new Person();//定義一個function,若是使用new"實例化",該function能夠看做是一個Class
person.name="Mark";
person.age="25";
person.work=function(){
alert(person.name+" hello...");
}
person.work();
複製代碼
// 帶參(用this關鍵字定義構造的上下文屬性)
function Pet(name,age,hobby){
this.name=name;//this做用域:當前對象
this.age=age;
this.hobby=hobby;
this.eat=function(){
alert("我叫"+this.name+",我喜歡"+this.hobby+",是個程序員");
}
}
var maidou =new Pet("麥兜",25,"coding");//實例化、建立對象
maidou.eat();//調用eat方法
複製代碼
Object.create
var p = {name:'smyhvae'};
var obj3 = Object.create(p); //此方法建立的對象,是用原型鏈鏈接的,obj3是實例,p是obj3的原型(name是p原型裏的屬性),構造函數是Objecet
複製代碼
function create(Con, ...args) {
let obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
複製代碼
首先函數接受不定量的參數,第一個參數爲構造函數,接下來的參數被構造函數使用
而後內部建立一個空對象 obj
由於 obj
對象須要訪問到構造函數原型鏈上的屬性,因此咱們經過 setPrototypeOf
將二者聯繫起來。這段代碼等同於 obj.__proto__ = Con.prototype
將 obj
綁定到構造函數上,而且傳入剩餘的參數
判斷構造函數返回值是否爲對象,若是爲對象就使用構造函數返回的值,不然使用 obj
,這樣就實現了忽略構造函數返回的原始值
參考連接:new操做符
function Foo()
仍是let a = { b : 1 }
,其實都是經過new產生的new Object()
的方式建立對象須要經過做用域鏈一層層找到 Object
,可是你使用字面量的方式就沒這個問題function Foo() {}
// function 就是個語法糖
// 內部等同於 new Function()
let a = { b: 1 }
// 這個字面量內部也是使用了 new Object()
複製代碼
prototype
(原型)prototype
裏找這個屬性,這個prototype
又會有本身的prototype
,因而就這樣一直找下去,也就是咱們平時所說的原型鏈的概念person1.constructor === Person
Person.prototype.constructor === Person
複製代碼
__proto__
指向其構造函數的原型person1.__proto__ === Person.prototype
複製代碼
function Parent1() {
this.name = 'parent1 的屬性';
}
function Child1() {
Parent1.call(this); //【重要】此處用 call 或 apply 都行:改變 this 的指向,parent的實例 --> 改成指向child的實例
this.type = 'child1 的屬性';
}
console.log(new Child1);
複製代碼
這種方式,雖然改變了 this 的指向,可是,Child1 沒法繼承 Parent1 的原型。也就是說,若是我給 Parent1 的原型增長一個方法,這個方法是沒法被 Child1 繼承的。
/* 經過原型鏈實現繼承 */
function Parent() {
this.name = 'Parent 的屬性';
}
function Child() {
this.type = 'Child 的屬性';
}
Child.prototype = new Parent(); //【重要】
console.log(new Child());
複製代碼
咱們把Parent
的實例賦值給了Child
的prototye
,從而實現繼承。此時,new Child.__proto__ === new Parent()
的結果爲true
這種繼承方式,Child 能夠繼承 Parent 的原型,但有個缺點:
若是修改 child1實例的name屬性,child2實例中的name屬性也會跟着改變。形成這種缺點的緣由是:child1和child2共用原型。即:chi1d1.__proto__ === child2__proto__
是嚴格相同。
用原型鏈實現對原型屬性和方法的繼承,用借用構造函數技術來實現實例屬性的繼承。
/* 組合方式實現繼承:構造函數、原型鏈 */
function Parent3() {
this.name = 'Parent 的屬性';
this.arr = [1, 2, 3];
}
function Child3() {
Parent3.call(this); //【重要1】執行 parent方法
this.type = 'Child 的屬性';
}
Child3.prototype = new Parent3(); //【重要2】第二次執行parent方法
var child = new Child3();
複製代碼
這種方式,能解決以前兩種方式的問題:既能夠繼承父類原型的內容,也不會形成原型裏屬性的修改。
這種方式的缺點是:讓父親Parent的構造方法執行了兩次。
extends
關鍵字主要用於類聲明或者類表達式中,以建立一個類,該類是另外一個類的子類。其中constructor
表示構造函數,一個類中只能有一個構造函數,有多個會報出SyntaxError
錯誤,若是沒有顯式指定構造方法,則會添加默認的 constructor
方法。
DOM0
element.onclick = function () {
}
複製代碼
一是在標籤內寫onclick事件 二是在JS寫onclick=function(){}函數
DOM2
//高版本瀏覽器
element.addEventListener('click', function () {
}, false);
//IE8及如下版本
element.attachEvent('onclick', function () {
});
//兼容寫法
/* * 參數: * element 要綁定事件的對象 * eventStr 事件的字符串(不要on) * callback 回調函數 */
function myBind(element , eventStr , callback){
if(element.addEventListener){
//大部分瀏覽器兼容的方式
element.addEventListener(eventStr , callback , false);
}else{
//IE8及如下
element.attachEvent("on"+eventStr , function(){
//在匿名函數 function 中調用回調函數callback
callback.call(element);
});
}
複製代碼
上面的第三參數中,true表示事件在捕獲階段觸發,false表示事件在冒泡階段觸發(默認)。若是不寫,則默認爲false。
DOM3
element.addEventListener('keyup', function () {
}, false);
複製代碼
DOM3中,增長了不少事件類型,好比鼠標事件、鍵盤事件等。
爲什麼事件沒有DOM1的寫法呢?由於,DOM1標準制定的時候,沒有涉及與事件相關的內容。
事件傳播的三個階段是:事件捕獲、事件目標、事件冒泡
事件從祖先元素往子元素查找(DOM樹結構),直到捕獲到事件目標 target。
addEventListener能夠捕獲事件
element.addEventListener('click', function () {
}, true);
複製代碼
參數爲true,表明事件在捕獲階段執行。
捕獲階段,事件依次傳遞的順序是:window --> document --> html--> body --> 父元素、子元素、目標元素。
window.addEventListener("click", function () {
alert("捕獲 window");
}, true);
document.addEventListener("click", function () {
alert("捕獲 document");
}, true);
document.documentElement.addEventListener("click", function () {
alert("捕獲 html");
}, true); //獲取html節點
document.body.addEventListener("click", function () {
alert("捕獲 body");
}, true); //獲取body節點
fatherBox.addEventListener("click", function () {
alert("捕獲 father");
}, true);
childBox.addEventListener("click", function () {
alert("捕獲 child");
}, true);
複製代碼
當一個元素上的事件被觸發的時候(好比說鼠標點擊了一個按鈕),一樣的事件將會在那個元素的全部祖先元素中被觸發。這一過程被稱爲事件冒泡;這個事件從原始元素開始一直冒泡到DOM樹的最上層。
通俗來說,冒泡指的是:子元素的事件被觸發時,父元素的一樣的事件也會被觸發。取消冒泡就是取消這種機制。
冒泡順序:
通常的瀏覽器: (除IE6.0以外的瀏覽器)
IE6.0:
不能冒泡的事件:
blur、focus、load、unload、onmouseenter、onmouseleave
檢查一個元素是否會冒泡:event.bubbles
阻止冒泡:
w3c的方法:(火狐、谷歌、IE11)
event.stopPropagation();
複製代碼
IE10如下:
event.cancelBubble = true
複製代碼
兼容代碼:
childBox.onclick = function(event){
event = event || window.event;
if(event && event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble = true;
}
}
複製代碼
上方代碼中,咱們對childBox進行了阻止冒泡,產生的效果是:事件不會繼續傳遞到 father、grandfather、body了
阻止默認事件
event.preventDefault();
複製代碼
function stopDefault( event ) {
//阻止默認瀏覽器動做(W3C)
if ( event && event.preventDefault )
event.preventDefault();
//IE中阻止函數器默認動做的方式
else
window.event.returnValue = false;
}
複製代碼
好比,已知<a>
標籤綁定了click事件,此時,若是給<a>
設置了這個方法,就阻止了連接的默認跳轉
阻止冒泡
代碼見上
設置事件優先級
event.stopImmediatePropagation();
複製代碼
好比說,我用addEventListener給某按鈕同時註冊了事件A、事件B。此時,若是我單擊按鈕,就會依次執行事件A和事件B。如今要求:單擊按鈕時,只執行事件A,不執行事件B。該怎麼作呢?這是時候,就能夠用到stopImmediatePropagation
方法了。作法是:在事件A的響應函數中加入這句話。
event.currentTarget //當前所綁定的事件對象。在事件委託中,指的是【父元素】。
event.target //當前被點擊的元素。在事件委託中,指的是【子元素】。
複製代碼
var myEvent = new Event('clickTest');
element.addEventListener('clickTest', function () {
console.log('smyhvae');
});
//元素註冊事件
element.dispatchEvent(myEvent); //注意,參數是寫事件對象 myEvent,不是寫 事件名 clickTest
複製代碼
事件代理/事件委託是利用事件冒泡的特性,將本應該綁定在多個元素上的事件綁定在他們的祖先元素上,尤爲在動態添加子元素的時候,能夠很是方便的提升程序性能,減少內存空間。