前排提示:感謝@程序員小鹿 寫的系列文章 這是我作的他文章的摘抄而已 用於學習html
String
Number
Null
Undefined
Symbol
Boolean
Object
Array
Function
Date
RegExp
stack
)和堆內存(heap
)Stack
自動分配內存,Heap
動態分配內存null
,減小內存消耗
stack
中,可按值直接訪問stack
中存放引用地址,在heap
中存放具體對象typeof null === Object
javaScript
中數據二進制前三位都是0的話,系統就會斷定爲Object
類型。前三位 | 類型 |
---|---|
000 | 對象 |
1 | 整型 |
010 | 雙精度 |
100 | 字符串 |
110 | 布爾 |
typeof
是一元運算符,返回String
類型。typeof
除了null
類型和對象類型不能準確判斷,其餘都能返回正確類型。
instanceof
判斷某個對象是否是另外一個對象的實例,返回boolean
instanceof
用來測試一個對象在其原型鏈中是否存在一個構造函數的prototype
屬性。javaScript
是弱類型語言,因此特定狀況咱們須要類型轉換即強制類型轉換java
轉
String
類型程序員
對於原始類型,轉String
會默認調用toString()
方法web
String(123) //"123"
String(true) //"true"
String(null) //"null"
String(undefined) //"undefined"
String([1,2,3]) //"1,2,3"
String(function(){111}) //"function(){111}"
String({}) //[Object Object]
複製代碼
轉Boolean類型ajax
除了幾個falsy值,''
、undefined
、null
、NAN
、0
、false
,其餘都轉爲true
編程
轉Number類型json
其餘類型 | 數字類型 |
---|---|
字符串 | 1. 數字轉化爲數字 2.其餘轉化爲NaN |
布爾類型 | true=>1,false=>0 |
null | 0 |
undefined | NaN |
數組 | 1.數組爲空轉化爲0 2.數組只有一個元素轉爲該元素 3.其餘轉化爲NaN |
空字符串 | 0 |
Number(10); // 10
Number('10'); // 10
Number(null); // 0
Number(''); // 0
Number(true); // 1
Number(false); // 0
Number([]); // 0
Number([1,2]); // NaN
Number('10a'); // NaN
Number(undefined); // NaN
複製代碼
對象類型轉原始類型數組
valueOf()
和toString()
方法String
,就調用toString()
valueOf()
toString()
加法運算瀏覽器
加法運算符是在運行時決定相加仍是鏈接,這被稱爲"重載"。bash
若是雙方都不是String
Boolean+Boolean
會轉化爲數字相加Boolean+Number
,布爾轉化爲數字再加Object+Number
,對象類型調用valueOf
,若是不是String
,Boolean
或者Number
類型,則繼續調用toString()
轉化爲字符串true + true // 2
1 + true // 2
[1] + 3 // '13'
複製代碼
數組的valueOf()
返回的仍是數組自己,因此會繼續調用toString()
字符串和字符串以及字符串與非字符串相加都會變鏈接
1 + 'b' // '1b'
false + 'b' // 'falseb'
'1' + 1 //'11'
複製代碼
其餘運算
其餘算術運算符,減法除法乘法一概所有轉爲數字,再運算
1 * '2' // 2
1 * [] // 0
複製代碼
&&
:全部條件爲真,才爲真||
:只要一個條件真,就真A&&B
若是A真就過,賦值爲B;若是A假就不過,直接就是A
A||B
若是A爲真就直接是A,若是A爲假就爲B
==
和===
===
是嚴格意義上的相等,必須類型和值都相等。
==
先判斷兩邊類型是否相等,若是不等,先轉換類型,再判斷值是否相等。
this
就是一個對象,不一樣狀況this
指向不一樣。
this
指向該對象var obj = {
name:'小鹿',
age: '21',
print: function(){
console.log(this)
console.log(this.name + ':' + this.age)
}
}
// 經過對象的方式調用函數
obj.print(); // this 指向 obj
複製代碼
this
指向window
對象function print(){
console.log(this);
}
// 全局調用函數
print(); // this 指向 window
複製代碼
new
的方式,this
永遠指向新建立的對象function Person(name,age){
this.name = name
this.age = age
console.log(this)
}
var xiaohu = new Person('小胡',24)// this = > xiaohu
複製代碼
this
箭頭函數沒有單獨的this
值,其this
與聲明所在的上下文相同。也就是說調用箭頭函數的時候,不會隱式調用this
參數,而是從定義時的函數繼承上下文
const obj = {
a:()=>{
console.log(this);
}
}
// 對象調用箭頭函數
obj.a(); // window
複製代碼
this
的指向能夠經過調用函數的call
、apply
、bind
來改變this
指向
var obj = {
name:'小鹿',
age:'22',
adress:'小鹿動畫學編程'
}
function print(){
console.log(this); // 打印 this 的指向
console.log(arguments); // 打印傳遞的參數
}
// 經過 call 改變 this 指向
print.call(obj,1,2,3);
// 經過 apply 改變 this 指向
print.apply(obj,[1,2,3]);
// 經過 bind 改變 this 的指向
let fn = print.bind(obj,1,2,3);
fn();
複製代碼
共同點:
this
指向,且第一個參數都是this
指向的對象不一樣點:
call
的傳參是單個傳遞,apply
後續參數是數組形式,bind
均可以call
和apply
是直接執行,bind
會返回一個函數,在調用時纔會執行箭頭函數不能用call
,apply
改變this
指向,由於他沒有本身的this
指向,若是用call
,apply
只會傳遞後續參數。
new的過程包括四個階段:
// new 生成對象的過程
// 一、生成新對象
// 二、連接到原型
// 三、綁定 this
// 四、返回新對象
// 參數:
// 一、Con: 接收一個構造函數
// 二、args:傳入構造函數的參數
function create(Con, ...args){
// 建立空對象
let obj = {};
// 設置空對象的原型(連接對象的原型)
obj._proto_ = Con.prototype;
// 綁定 this 並執行構造函數(爲對象設置屬性)
let result = Con.apply(obj,args)
// 若是 result 沒有其餘選擇的對象,就返回 obj 對象
return result instanceof Object ? result : obj;
}
// 構造函數
function Test(name, age) {
this.name = name
this.age = age
}
Test.prototype.sayName = function () {
console.log(this.name)
}
// 實現一個 new 操做符
const a = create(Test,'小鹿','23')
console.log(a.age)
複製代碼
經常使用建立對象的方式:
new
其餘建立對象的方式:
Object.create()
字面量建立對象的優點:
new
同樣,解析器須要順着做用域鏈從當前做用域開始查找,若是在當前做用域找到了名爲Object()
的函數就執行,若是沒找到就順着繼續向上找,直到找到全局Object()
構造函數爲止。Object()
構造函數能夠接受參數,經過這個參數能夠吧對象實例的建立過程委託給另外一個內置構造函數,並返回另一個對象實例,而這每每不是想要的。對於Object.create()
方式:Object.create(proto,[propertiesObject])
var People = function (name){
this.name = name;
};
People.prototype.sayName = function (){
console.log(this.name);
}
function Person(name, age){
this.age = age;
People.call(this, name); // 使用call,實現了People屬性的繼承
};
// 使用Object.create()方法,實現People原型方法的繼承,而且修改了constructor指向
Person.prototype = Object.create(People.prototype, {
constructor: {
configurable: true,
enumerable: true,
value: Person,
writable: true
}
});
Person.prototype.sayAge = function (){
console.log(this.age);
}
var p1 = new Person('person1', 25);
p1.sayName(); //'person1'
p1.sayAge(); //25
複製代碼
new
和字面量建立的對象的原型指向Object.prototype
,會繼承Object
的屬性和方法Object.create(null)
建立的對象,其原型指向null
,null
做爲原型鏈的頂端,沒有也不會繼承任何屬性和方法規定變量和函數的可以使用範圍叫作做用域。
每一個函數都會有一個做用域,查找變量或函數時,由局部做用域到全局做用域依次查找,這些做用域的集合就叫作做用域鏈。
函數執行,造成一個私有的做用域,保護裏面的私有變量不受外界干擾,除了保護私有變量外,還能夠保存一些內容,這種模式叫作閉包。
再也不用到的內存,系統就回收。
內部函數引用着外部的函數的變量,外部函數儘管執行完畢,做用域也不會銷燬。從而造成了一種不銷燬的私有做用域。
內部函數能夠訪問外部函數做用域,外部函數不能獲取內部函數的做用於變量。
一個函數裏邊再定義一個函數,內部函數一直保持有對外部函數做用域的訪問權限。
兩個做用:保護和保存。
// 事件綁定引起的索引問題
var btnBox = document.getElementById('btnBox'),
inputs = btnBox.getElementsByTagName('input')
var len = inputs.length;
for(var i = 0; i < len; i++){
inputs[i].onclick = function () {
alert(i)
}
}
複製代碼
運行程序得出的結果都是len的數值。
由於全部事件綁定都是異步的,當觸發點擊事件,執行方法的時候,循環早就結束了。
同步:JS中當前這個任務沒完成,下面的任務都不會執行,只有等當前完全完成,纔會執行下面的任務。 異步:JS當前任務沒有完成,須要等一會再完成,但此時咱們能夠繼續執行下面的任務。
解決方案:
當點擊事件執行的時候,就會在私有做用域查找i的值,此時私有做用域沒有i,就會去全局做用域查找,此時全局做用於的i已經被改變了,因此要建立一個私有做用域的i。
for(var i = 0;i<length;i++){
~function(i){
inputs[i].onclick = function(){
alert(this.myIndex)
}
}(i)
}
複製代碼
閉包既有優勢也有缺點。
優勢是經過閉包解決循環幾回就建立幾個私有做用域,而後每一個私有做用域都有一個私有變量i。
缺點就是生成多個不銷燬的私有做用域,對性能有影響。
原型:每一個JS對象都有__proto__屬性,這個屬性指向了原型。
原型鏈:多個對象經過__proto__的方式鏈接起來。
經過判斷該對象的原型鏈中是否能夠找到該構造類型的prototype類型
//一、當用調用 call 方法時,this 帶邊 son 。
//二、此時 Father 構造函數中的 this 指向 son。
//三、也就是說 son 有了 colors 的屬性。
//四、每 new 一個 son ,都會產生不一樣的對象,每一個對象的屬性都是相互獨立的。
function Father(){
this.colors = ["red","blue","green"];
}
function Son(){
// this 是經過 new 操做內部的新對象 {} ,
// 此時 Father 中的 this 就是爲 Son 中的新對象{}
// 新對象就有了新的屬性,並返回獲得 new 的新對象實例
// 繼承了Father,且向父類型傳遞參數
Father.call(this);
}
let s = new Son();
console.log(s.color)
複製代碼
基本思想:在子類的構造函數的內部調用父類的構造函數。 優勢:
缺點:
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
// 方法定義在原型對象上(共享)
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
// 子類繼承父類的屬性
Father.call(this,name); //繼承實例屬性,第一次調用 Father()
// 每一個實例都有本身的屬性
this.age = age;
}
// 子類和父類共享的方法(實現了父類屬性和方法的複用)
Son.prototype = new Father(); //繼承父類方法,第二次調用 Father()
// 子類實例對象共享的方法
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5
var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10
複製代碼
基本思想:
優勢:
缺點:組合繼承調用了兩次父類的構造函數,形成了沒必要要的消耗。
function object(o){
function F(){}
F.prototype = o;
// 每次返回的 new 是不一樣的
return new F();
}
var person = {
friends : ["Van","Louis","Nick"]
};
// 實例 1
var anotherPerson = object(person);
anotherPerson.friends.push("Rob");
// 實例 2
var yetAnotherPerson = object(person);
yetAnotherPerson.friends.push("Style");
// 都添加至原型對象的屬性(所共享)
alert(person.friends); // "Van,Louis,Nick,Rob,Style"
複製代碼
基本思想:建立臨時的構造函數,將傳入的對象做爲該構造函數的原型對象,而後返回新構造函數的實例。
淺拷貝:object產生的對象是不相同的,可是原型對象都是person對象,所改變存在原型對象的屬性被全部生成的實例所共享,不只Person擁有,並且子類生成的實例也共享。
Object.create():在ECMAScript5中新增了此方法。
function createAnother(original){
var clone = object(original); // 經過調用object函數建立一個新對象
clone.sayHi = function(){ // 以某種方式來加強這個對象
alert("hi");
};
return clone; //返回這個對象
}
複製代碼
基本思想:沒必要爲了指定子類的原型而調用父類的構造函數。
優勢:解決組合繼承中兩次調用構造函數的開銷。
再也不用到的內存,沒有及時釋放,就是內存泄漏。
之因此會有垃圾回收機制,是由於js中字符串、對象、數組等只有肯定固定大小時,纔會動態分配內存,而使用完必須釋放,不然會消耗完全部內存。
js與其餘語言不一樣,具備自動垃圾收集機制。
找出那些再也不使用的變量,而後釋放內存,垃圾回收器會按照固定的時間間隔,週期性的執行垃圾回收。
垃圾回收器在運行的時候,會給存儲在內存中的全部變量都加上標記,而後會去掉環境中變量以及被環境中的變量引用的變量的標記。剩下的就被視爲要刪除的變量。
其實現原理就是經過判斷一個變量是否在執行環境中被引用,來進行標記刪除。
跟蹤記錄每一個值被引用的次數。
當聲明變量並將一個引用類型的值賦值給該變量時,則這個值引用次數加1,同一值被賦予另外一個變量,該值引用次數加1。當引用該值的變量被另外一個值取代,則引用計數減1,當計數爲0時,就沒法訪問了,就會收回。
缺陷:兩個對象的相互循環引用時,在函數執行完成後,兩個對象相互引用計數並未歸零,全部沒法回收。
常見的就是在IE BOM和DOM中,使用的對象並非js對象,因此垃圾回收是基於計數策略。
雖然js內存都是自動管理,但仍是有問題,好比分配給web瀏覽器的可用內存數量一般比分配給桌面應用的少。
爲了能讓頁面得到最好的性能,必須確保js變量佔用最少的內存,因此最好將不用的變量引用釋放,也叫解除引用。
var a = 20;
alert(a + 100);
var a = null;
複製代碼
只有與環境變量失去引用的變量纔會被標記回收,將對象引用設置爲null,就失去引用,等待被回收。
對基本類型的拷貝就是對值進行拷貝,而對於引用類型來講,拷貝的不是值,而是值的地址,最後兩個變量的地址指向的是同一個值。
var a = 10;
var b = a;
b = 30;
console.log(a); // 10值
console.log(b); // 30值
var obj1 = new Object();
var obj2 = obj1;
obj2.name = "小鹿";
console.log(obj1.name); // 小鹿
複製代碼
要想將obj1,obj2的關係斷開,不讓其指向同一個地址,分爲淺拷貝和深拷貝。
本身實現一個淺拷貝:
function shallowClone(o){
const obj = {};
for(let i in o){
obj[i] = o[i]
}
return obj;
}
複製代碼
let a = {c: 1}
let b = {...a}
a.c = 2
console.log(b.c) // 1
複製代碼
let a = {c: 1}
let b = Object.assign({}, a)
a.c = 2
console.log(b.c) // 1
複製代碼
深拷貝須要在淺拷貝的基礎上加上遞歸
比較簡單的實現方法:利用JSON.parse(JSON.stringify(obj))
function clonebyJSON(source){
return JSON.parse(JSON.stringify(source))
}
複製代碼
但他內部也是使用的遞歸,遞歸到必定深度會爆棧,但不會出現循環引用問題。
因爲JavaScript是單線程,因此會有阻塞問題,當一個任務執行完成後才能執行下一個任務,這樣就會出現頁面卡死。
單線程是由一些與用戶的互動以及操做DOM相關的操做決定了JS要使用單線程,不然會帶來複雜的同步問題。須要加鎖。
H5標準規定容許js建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。
最先是使用回調函數,回調函數不是直接調用,而是在特定的事件或條件發生時另外一方調用,用於對該事件或條件進行響應。
好比Ajax回調:
$.ajax({
type:'post',
url:'test.json',
dataType:'json',
success:function(res){
//成功回調
},
fail:function(err){
//響應失敗回調
}
})
複製代碼
可是若是某個請求存在依賴性:
$.ajax({
type:'post',
url:'test.json',
dataType:'json',
success:function(res){
$.ajax({
type:'post',
url:'xxx?id='+res.id,
success:function(res){
$.ajax({
...//循環
})
}
})
},
fail:function(err){
//響應失敗回調
}
})
複製代碼
這樣就會不斷循環嵌套,稱爲回調地獄。
缺點:
爲何不能捕獲異常?
這和js運行機制相關,異步任務執行完成會加入任務隊列,當執行棧中沒有可執行任務了,主線程去除任務隊列中的異步任務併入棧執行,當異步任務執行時,捕獲異常的函數已經在執行棧內退出了,因此異常沒法捕獲。
爲何不能return
return只能終止回調函數的執行,而不能終止外部代碼的執行。
ES6給了三種解決方案:Generator、Promise、async/await
能夠理解爲代碼執行的環境。
JS執行上下文分爲三種:
代碼執行的時候,遇到一個執行上下文就將其依次壓入執行棧。
當代碼執行時,先執行位於棧頂的執行上下文中的代碼,當棧頂的執行上下文代碼執行完畢後就出棧,而後執行下一個位於棧頂的執行上下文。
通常包括:
通常包括:
nextTick隊列會比Promise先執行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>消息運行機制</title>
</head>
<body>
</body>
<script>
console.log('1');
setTimeout(() => {
console.log('2')
}, 1000);
new Promise((resolve, reject) => {
console.log('3');
resolve();
console.log('4');
}).then(() => {
console.log('5');
});
console.log('6');// 1,3,4,6,5,2
</script>
</html>
複製代碼