var executeTimes = 0;
var intervalTime = 500;
var intervalId = null;
// 放開下面的註釋運行setInterval的Demo
intervalId = setInterval(intervalFun,intervalTime);
// 放開下面的註釋運行setTimeout的Demo
// setTimeout(timeOutFun,intervalTime);
function intervalFun(){
executeTimes++;
console.log("doIntervalFun——"+executeTimes);
if(executeTimes==5){
clearInterval(intervalId);
}
}
function timeOutFun(){
executeTimes++;
console.log("doTimeOutFun——"+executeTimes);
if(executeTimes<5){
setTimeout(arguments.callee,intervalTime);
}
}
複製代碼
代碼比較簡單,咱們只是在setTimeout的方法裏面又調用了一次setTimeout,就能夠達到間歇調用的目的。javascript
重點來了,爲何做者建議咱們使用setTimeout代替setInterval呢?setTimeout式的間歇調用和傳統的setInterval間歇調用有什麼區別呢?vue
區別在於,setInterval間歇調用,是在前一個方法執行前,就開始計時,好比間歇時間是500ms,那麼無論那時候前一個方法是否已經執行完畢,都會把後一個方法放入執行的序列中。這時候就會發生一個問題,假如前一個方法的執行時間超過500ms,加入是1000ms,那麼就意味着,前一個方法執行結束後,後一個方法立刻就會執行,由於此時間歇時間已經超過500ms了。java
var executeTimes = 0;
var intervalTime = 500;
var intervalId = null;
var oriTime = new Date().getTime();
// 放開下面的註釋運行setInterval的Demo
// intervalId = setInterval(intervalFun,intervalTime);
// 放開下面的註釋運行setTimeout的Demo
setTimeout(timeOutFun,intervalTime);
function intervalFun(){
executeTimes++;
var nowExecuteTimes = executeTimes;
var timeDiff = new Date().getTime() - oriTime;
console.log("doIntervalFun——"+nowExecuteTimes+", after " + timeDiff + "ms");
var delayParam = 0;
sleep(1000);
console.log("doIntervalFun——"+nowExecuteTimes+" finish !");
if(executeTimes==5){
clearInterval(intervalId);
}
}
function timeOutFun(){
executeTimes++;
var nowExecuteTimes = executeTimes;
var timeDiff = new Date().getTime() - oriTime;
console.log("doTimeOutFun——"+nowExecuteTimes+", after " + timeDiff + "ms");
var delayParam = 0;
sleep(1000);
console.log("doTimeOutFun——"+nowExecuteTimes+" finish !");
if(executeTimes<5){
setTimeout(arguments.callee,intervalTime);
}
}
function sleep(sleepTime){
var start=new Date().getTime();
while(true){
if(new Date().getTime()-start>sleepTime){
break;
}
}
}
複製代碼
(這裏使用大牛提供的sleep函數來模擬函數運行的時間) 執行setInterval的Demo方法,看控制檯node
doIntervalFun——1, after 500ms
VM2854:19 doIntervalFun——1 finish !
VM2854:16 doIntervalFun——2, after 1503ms
VM2854:19 doIntervalFun——2 finish !
VM2854:16 doIntervalFun——3, after 2507ms
VM2854:19 doIntervalFun——3 finish !
VM2854:16 doIntervalFun——4, after 3510ms
VM2854:19 doIntervalFun——4 finish !
VM2854:16 doIntervalFun——5, after 4512ms
VM2854:19 doIntervalFun——5 finish !
複製代碼
能夠發現,fun2和fun1開始的間歇接近1000ms,恰好就是fun1的執行時間,也就意味着fun1執行完後fun2立刻就執行了,和咱們間歇調用的初衷背道而馳。chrome
咱們註釋掉setInterval的Demo方法,放開setTimeout的Demo方法,運行,查看控制檯數組
doTimeOutFun——1, after 500ms
VM2621:32 doTimeOutFun——1 finish !
VM2621:29 doTimeOutFun——2, after 2001ms
VM2621:32 doTimeOutFun——2 finish !
VM2621:29 doTimeOutFun——3, after 3503ms
VM2621:32 doTimeOutFun——3 finish !
VM2621:29 doTimeOutFun——4, after 5004ms
VM2621:32 doTimeOutFun——4 finish !
VM2621:29 doTimeOutFun——5, after 6505ms
VM2621:32 doTimeOutFun——5 finish !
複製代碼
這下終於正常了,fun1和fun2相差了1500ms = 1000 + 500,fun2在fun1執行完的500ms後執行。promise
var person = function(){
//變量做用域爲函數內部,外部沒法訪問,不會與外部變量發生重名衝突
var name = "FE";
return {
//管理私有變量
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
複製代碼
缺點: 形成內存消耗過大,若是處理不當,會形成內存泄漏瀏覽器
大多數狀況下,咱們都要對數組進行遍歷,而後常常用到的兩個方法就是forEach和map方法。 先來講說它們的共同點緩存
1.map方法返回一個新的數組,數組中的元素爲原始數組調用函數處理後的值。 2.map方法不會對空數組進行檢測,map方法不會改變原始數組。 3.瀏覽器支持:chrome、Safari1.5+、opera都支持,IE9+,閉包
array.map(function(item,index,arr){},thisValue)
var arr = [0,2,4,6,8];
var str = arr.map(function(item,index,arr){
console.log(this); //window
console.log("原數組arr:",arr); //注意這裏執行5次
return item/2;
},this);
console.log(str);//[0,1,2,3,4]
複製代碼
若arr爲空數組,則map方法返回的也是一個空數組。
forEach方法
Array.forEach(function(item,index,arr){},this)
var arr = [0,2,4,6,8];
var sum = 0;
var str = arr.forEach(function(item,index,arr){
sum += item;
console.log("sum的值爲:",sum); //0 2 6 12 20
console.log(this); //window
},this)
console.log(sum);//20
console.log(str); //undefined
複製代碼
不管arr是否是空數組,forEach返回的都是undefined。這個方法只是將數組中的每一項做爲callback的參數執行一次。
遍歷數組一般使用for循環,ES5的話也可使用forEach,ES5具備遍歷數組功能的還有map、filter、some、every、reduce、reduceRight等,只不過他們的返回結果不同。可是使用foreach遍歷數組的話,使用break不能中斷循環,使用return也不能返回到外層函數。
Array.prototype.method=function(){
  console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="數組"
for (var index in myArray) {
console.log(myArray[index]);
}
複製代碼
使用for in 也能夠遍歷數組,可是會存在如下問題:
index索引爲字符串型數字,不能直接進行幾何運算
遍歷順序有可能不是按照實際數組的內部順序
使用for in會遍歷數組全部的可枚舉屬性,包括原型。例如上慄的原型方法method和name屬性
因此for in更適合遍歷對象,不要使用for in遍歷數組。
那麼除了使用for循環,如何更簡單的正確的遍歷數組達到咱們的指望呢(即不遍歷method和name),ES6中的for of更勝一籌.
Array.prototype.method=function(){
  console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="數組";
for (var value of myArray) {
console.log(value);
}
複製代碼
記住,for in遍歷的是數組的索引(即鍵名),而for of遍歷的是數組元素值。
for of遍歷的只是數組內的元素,而不包括數組的原型屬性method和索引name
遍歷對象 一般用for in來遍歷對象的鍵名
Object.prototype.method=function(){
  console.log(this);
}
var myObject={
  a:1,
  b:2,
  c:3
}
for (var key in myObject) {
console.log(key);
}
複製代碼
for in 能夠遍歷到myObject的原型方法method,若是不想遍歷原型方法和屬性的話,能夠在循環內部判斷一下,hasOwnPropery
方法能夠判斷某屬性是不是該對象的實例屬性
for (var key in myObject) {
  if(myObject.hasOwnProperty(key)){
    console.log(key);
  }
}
複製代碼
一樣能夠經過ES5的Object.keys(myObject)
獲取對象的實例屬性組成的數組,不包括原型方法和屬性。
Object.prototype.method=function(){
  console.log(this);
}
var myObject={
  a:1,
  b:2,
  c:3
}
Object.keys(myObject).forEach(function(key,index){  
console.log(key,myObject[key])
})
複製代碼
EventEmitter 的核心就是事件觸發與事件監聽器功能的封裝。 當你回答出 vue 中用 emit 通訊的時候,就要當心了。EventEmitter 方法主要包含了 on,emit,once,off方法。
class Event {
constructor() {
this.events = Object.create(null);
}
on(name, fn) {
if (!this.events[name]) {
this.events[name] = []
}
this.events[name].push(fn);
return this;
}
emit(name, ...args) {
if (!this.events[name]) {
return this;
}
const fns = this.events[name]
fns.forEach(fn => fn.call(this, ...args))
return this;
}
off(name,fn) {
if (!this.events[name]) {
return this;
}
if (!fn) {
this.events[name] = null
return this
}
const index = this.events[name].indexOf(fn);
this.events[name].splice(index, 1);
return this;
}
once(name,fn) {
const only = () => {
fn.apply(this, arguments);
this.off(name, only);
};
this.on(name, only);
return this;
}
}
複製代碼
var 第一個就是做用域的問題,var不是針對一個塊級做用域,而是針對一個函數做用域。舉個例子:
function runTowerExperiment(tower, startTime) {
var t = startTime;
tower.on("tick", function () {
... code that uses t ...
});
... more code ...
}
複製代碼
這樣是沒什麼問題的,由於回調函數中能夠訪問到變量t,可是若是咱們在回調函數中再次命名了變量t呢?
function runTowerExperiment(tower, startTime) {
var t = startTime;
tower.on("tick", function () {
... code that uses t ...
if (bowlingBall.altitude() <= 0) {
var t = readTachymeter();
...
}
});
... more code ...
}
複製代碼
後者就會將前者覆蓋。
第二個就是循環的問題。 看下面例子:
var messages = ["Meow!", "I'm a talking cat!", "Callbacks are fun!"];
for (var i = 0; i < messages.length; i++) {
setTimeout(function () {
document.write(messages[i]);
},i*1500);
}
複製代碼
輸出結果是:undefined 由於for循環後,i置爲3,因此訪問不到其值。
let 爲了解決這些問題,ES6提出了let語法。let能夠在{},if,for裏聲明,其用法同var,可是做用域限定在塊級。可是javascript中不是沒有塊級做用域嗎?這個咱們等會講。還有一點很重要的就是let定義的變量不存在變量提高。
變量提高 這裏簡單提一下什麼叫作變量提高。
var v='Hello World';
(function(){
alert(v);
var v='I love you';
})()
複製代碼
上面的代碼輸出結果爲:undefined。
爲何會這樣呢?這就是由於變量提高,變量提高就是把變量的聲明提高到函數頂部,好比:
(function(){
var a='One';
var b='Two';
var c='Three';
})()
複製代碼
實際上就是:
(function(){
var a,b,c;
a='One';
b='Two';
c='Three';
})()
複製代碼
因此咱們剛纔的例子其實是:
var v='Hello World';
(function(){
var v;
alert(v);
v='I love you';
})()
複製代碼
因此就會返回undefined啦。
這也是var的一個問題,而咱們使用let就不會出現這個問題。由於它會報語法錯誤:
{
console.log( a ); // undefined
console.log( b ); // ReferenceError!
var a;
let b;
}
複製代碼
再來看看let的塊級做用域。
function getVal(boo) {
if (boo) {
var val = 'red'
// ...
return val
} else {
// 這裏能夠訪問 val
return null
}
// 這裏也能夠訪問 val
}
複製代碼
而使用let後:
function getVal(boo) {
if (boo) {
let val = 'red'
// ...
return val
} else {
// 這裏訪問不到 val
return null
}
// 這裏也訪問不到 val
}
複製代碼
一樣的在for循環中:
function func(arr) {
for (var i = 0; i < arr.length; i++) {
// i ...
}
// 這裏訪問獲得i
}
複製代碼
使用let後:
function func(arr) {
for (let i = 0; i < arr.length; i++) {
// i ...
}
// 這裏訪問不到i
}
複製代碼
也就是說,let只能在花括號內部起做用。
const 再來講說const,const表明一個值的常量索引。
const aa = 11;
alert(aa) //11
aa = 22;
alert(aa) //報錯
複製代碼
可是常量的值在垃圾回收前永遠不能改變,因此須要謹慎使用。
還有一條須要注意的就是和其餘語言同樣,常量的聲明必須賦予初值。即便咱們想要一個undefined的常量,也須要聲明:
const a = undefined;
複製代碼
塊級做用域 最後提一下剛纔說到的塊級做用域。
在以前,javascript是沒有塊級做用域的,咱們都是經過()來模擬塊級做用域。
(function(){
//這裏是塊級做用域
})();
複製代碼
可是在ES6中,{}就能夠直接代碼塊級做用域。因此{}內的內容是不能夠在{}外訪問獲得的。
咱們能夠看看以下代碼:
if (true) {
function foo() {
console.log("1" );
}
}else {
function foo() {
console.log("2" );
}
}
foo(); // 1
複製代碼
在咱們所認識的javascript裏,這段代碼的輸出結果爲1。這個叫作函數聲明提高,不只僅提高了函數名,也提高了函數的定義。
可是在ES6裏,這段代碼或拋出ReferenceErroe錯誤。由於{}的塊級做用域,致使外面訪問不到foo(),也就是說函數聲明和let定義變量同樣,都被限制在塊級做用域中了。
從promise、process.nextTick、setTimeout出發,談談Event Loop中的Job queue
簡要介紹:談談promise.resove,setTimeout,setImmediate,process.nextTick在EvenLoop隊列中的執行順序
1.問題的引出 event loop都不陌生,是指主線程從「任務隊列」中循環讀取任務,好比
例1:
setTimeout(function(){console.log(1)},0);
console.log(2)
//輸出2,1
複製代碼
在上述的例子中,咱們明白首先執行主線程中的同步任務,當主線程任務執行完畢後,再從event loop中讀取任務,所以先輸出2,再輸出1。
event loop讀取任務的前後順序,取決於任務隊列(Job queue)中對於不一樣任務讀取規則的限定。好比下面一個例子:
例2:
setTimeout(function () {
console.log(3);
}, 0);
Promise.resolve().then(function () {
console.log(2);
});
console.log(1);
//輸出爲 1 2 3
複製代碼
先輸出1,沒有問題,由於是同步任務在主線程中優先執行,這裏的問題是setTimeout和Promise.then任務的執行優先級是如何定義的。
2 . Job queue中的執行順序 在Job queue中的隊列分爲兩種類型:macro-task和microTask。咱們舉例來看執行順序的規定,咱們設
macro-task隊列包含任務: a1, a2 , a3 micro-task隊列包含任務: b1, b2 , b3
執行順序爲,首先執行marco-task隊列開頭的任務,也就是 a1 任務,執行完畢後,在執行micro-task隊列裏的全部任務,也就是依次執行b1, b2 , b3,執行完後清空micro-task中的任務,接着執行marco-task中的第二個任務,依次循環。
瞭解完了macro-task和micro-task兩種隊列的執行順序以後,咱們接着來看,真實場景下這兩種類型的隊列裏真正包含的任務(咱們以node V8引擎爲例),在node V8中,這兩種類型的真實任務順序以下所示:
macro-task(宏任務)隊列真實包含任務: script(主程序代碼),setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task(微任務)隊列真實包含任務: process.nextTick, Promises, Object.observe, MutationObserver
由此咱們獲得的執行順序應該爲:
script(主程序代碼)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering
在ES6中macro-task隊列又稱爲ScriptJobs,而micro-task又稱PromiseJobs
3 . 真實環境中執行順序的舉例
(1) setTimeout和promise
例3:
setTimeout(function () {
console.log(3);
}, 0);
Promise.resolve().then(function () {
console.log(2);
});
console.log(1);
複製代碼
咱們先以第1小節的例子爲例,這裏遵循的順序爲:
script(主程序代碼)——>promise——>setTimeout 對應的輸出依次爲:1 ——>2————>3 (2) process.nextTick和promise、setTimeout
例子4:
setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){
console.log(2);
resolve();
}).then(function(){console.log(3)
}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);
//輸出2,6,5,3,4,1
複製代碼
這個例子就比較複雜了,這裏要注意的一點在定義promise的時候,promise構造部分是同步執行的,這樣問題就迎刃而解了。
首先分析Job queue的執行順序:
script(主程序代碼)——>process.nextTick——>promise——>setTimeout
I) 主體部分: 定義promise的構造部分是同步的, 所以先輸出2 ,主體部分再輸出6(同步狀況下,就是嚴格按照定義的前後順序)
II)process.nextTick: 輸出5
III)promise: 這裏的promise部分,嚴格的說實際上是promise.then部分,輸出的是3,4
IV) setTimeout : 最後輸出1
綜合的執行順序就是: 2——>6——>5——>3——>4——>1
(3)更復雜的例子
setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){
console.log(2);
setTimeout(function(){resolve()},0)
}).then(function(){console.log(3)
}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);
//輸出的是 2 6 5 1 3 4
複製代碼
這種狀況跟咱們(2)中的例子,區別在於promise的構造中,沒有同步的resolve,所以promise.then在當前的執行隊列中是不存在的,只有promise從pending轉移到resolve,纔會有then方法,而這個resolve是在一個setTimout時間中完成的,所以3,4最後輸出。
ECMAScript是鬆散類型的,一次須要一種手段來檢測給定變量的數據類型,typeof操做符(注意不是函數哈!)就是負責提供這方面信息的
typeof 能夠用於檢測基本數據類型和引用數據類型。
語法格式以下:
typeof variable
複製代碼
返回6種String類型的結果:
console.log(typeof 'hello'); // "string"
console.log(typeof null); // "object"
console.log(typeof (new Object())); // "object"
console.log(typeof(function(){})); // "function"
複製代碼
typeof主要用於檢測基本數據類型:數值、字符串、布爾值、undefined, 由於typeof用於檢測引用類型值時,對於任何Object對象實例(包括null),typeof都返回"object"值,沒辦法區分是那種對象,對實際編碼用處不大。
instanceof 用於判斷一個變量是否某個對象的實例
在檢測基本數據類型時typeof是很是得力的助手,但在檢測引用類型的值時,這個操做符的用處不大,一般,咱們並非想知道某個值是對象,而是想知道它是什麼類型的對象。此時咱們可使用ECMAScript提供的instanceof操做符。
語法格式以下:
result = variable instanceof constructor
複製代碼
返回布爾類型值:
function Person(){}
function Animal(){}
var person1 = new Person();
var animal1 = new Animal();
console.log(person1 instanceof Person); // true
console.log(animal1 instanceof Person); // false
console.log(animal1 instanceof Object); // true
console.log(1 instanceof Person); //false
var oStr = new String("hello world");
console.log(typeof(oStr)); // object
console.log(oStr instanceof String);
console.log(oStr instanceof Object);
// 判斷 foo 是不是 Foo 類的實例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo);
// instanceof 在繼承中關係中的用法
console.log('instanceof 在繼承中關係中的用法');
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();
var fo = new Foo();
console.log(fo instanceof Foo);
console.log(fo instanceof Aoo)
複製代碼
根據規定,全部引用類型的值都是Object的實例。所以,在檢測一個引用類型值和Object構造函數時,instanceof操做符會始終返回true。若是使用instanceof 操做符檢測基本類型值時,該操做符會始終返回false,由於基本類型不是對象。
console.log(Object.prototype.toString.call(null));
// [object Null]
undefined
console.log(Object.prototype.toString.call([1,2,3]));
//[object Array]
undefined
console.log(Object.prototype.toString.call({}));
// [object Object]
複製代碼
原型鏈繼承
定義 利用原型讓一個引用類型繼承另一個引用類型的屬性和方法 代碼
function SuperType(){
this.property = 'true';
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subProperty = 'false';
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subProperty;
}
var instance = new SubType();
alert(instance.getSuperValue());//true
複製代碼
構造函數繼承 定義 在子類型構造函數的內部調用超類型的構造函數
代碼
function SuperType(){
this.colors = ['red','yellow'];
}
function SubType(){
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push('black');
var instance2 = new SubType();
instance2.colors.push('white');
alert(instance1.colors);//'red','yellow','black'
alert(instance2.colors);//'red','yellow','white'
複製代碼
組合繼承
定義 使用原型鏈實現多原型屬性和方法的繼承,使用構造函數實現實例的繼承
代碼
function SuperType(name){
this.name = name;
this.colors = ['red','black'];
}
SuperType.prototype.sayName = function() {
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.protptype = new SuperType();
SubType.protptype.sayAge = function(){
alert(this.age);
}
複製代碼