JS知識體系梳理-3

棧內存與堆內存

棧內存:做用域javascript

1.提供一個供JS代碼自上而下執行的環境(代碼都是在棧中執行的) 2.因爲基本數據類型值比較簡單,他們都是直接在棧內存中開闢一個位置,把值直接存儲進去的java

當棧內存被銷燬,存儲的基礎值也隨之銷燬面試

堆內存:引用值對應的空間ajax

1.存儲引用類型值的編程

  • 對象:存儲的是鍵值對
  • 函數:代碼字符串

當前堆內存釋放銷燬,那麼這個引用值完全沒了小程序

堆內存的釋放設計模式

當堆內存沒有被任何的變量或者其它東西所佔用,瀏覽器會在空閒的時候,自主的進行內存回收,把全部不被佔用的堆內存銷燬(谷歌瀏覽器)[IE瀏覽器經過計數器來進行內存釋放]數組

銷燬方法: xxx=null
經過空對象指針null可讓原始變量(或者其它東西)誰都不指向,那麼原有被佔用的堆內存就沒有被東西佔用了,瀏覽器會銷燬它瀏覽器

ES5變量提高機制

定義:當棧內存(做用域)造成以後,JS代碼自上而下執行以前,瀏覽器首先會把全部帶var(聲明)function(聲明並定義)關鍵詞的進行提早的聲明或者定義,這種預先處理機制稱爲"變量提高"性能優化

  • 聲明(declare): var a/function sum (默認值undefined)
  • 定義(defined):a=12 (定義其實就是賦值操做)

變量提高細節知識點

1.變量提高只發生在當前做用域(例如:開始加載頁面的時候只對全局做用域下的變量進行提高,由於此時函數中存儲的都是字符串而已)

2.在全局做用域下聲明的函數或者變量是"全局變量",同理,在私有做用域下聲明的變量是"私有變量"[帶VAR/FUNCTION的纔是聲明]

3.瀏覽器很懶,作過的事情**不會重複**執行第二遍,也就是,當代碼執行遇到建立函數這部分代碼後,直接的跳過便可(由於在提高階段就已經完成函數的賦值操做了)

私有做用域造成後,也不是當即執行代碼,而是先進行變量提高(變量提高前,先進行形參賦值)

在ES3/ES5語法規範中,只有全局做用域和函數執行的私有做用域(棧內存),其它大括號不會造成棧內存

知識點補充:邏輯與&&和邏輯或||

優先級:邏輯與的優先級高於邏輯或

console.log(0||1&&2||0||3&&2||1);//=>2
複製代碼

邏輯與

A&&B,驗證A的真假,爲真結果是B,爲假結果爲A

var b=1&&2//=>b=2
複製代碼

邏輯與的實戰

//=>只有當傳遞的值是函數的時候咱們才讓其執行

~function (caller){
    caller&&caller()

}(function (){console.log('ok');});
複製代碼

邏輯或

A||B,驗證A的真假,爲真結果爲A,爲假結果爲B

var a=1||2//=>a=1
複製代碼

邏輯或的實戰

//=>"給形參賦值默認值:驗證傳遞的參數值,若是沒有傳遞實參,賦值爲0"
function fn(x){
	x=x||0;
	//=>若是x沒有傳遞值,x=undefined=>x=undefined||0
}
複製代碼

特殊狀況

在全局做用域下聲明一個變量,也至關於給window全局對象設置了一個屬性,變量的值就是屬性值(私有做用域中聲明的私有變量和window沒啥關係)

//=>1.先進行變量提高 var a;此時同時也給window全局設置了一個屬性名爲a,屬性值爲undefined的屬性

console.log(a);//=>undefined
console.log(window.a)//=>undefined
console.log('a' in window)//=>true in:檢測某個屬性是否隸屬於這個對象

var a=12;
console.log(a);//=>全局變量a 12
console.log(window.a);//=>window的一個屬性名a 12

a=13;
console.log(window.a);//=>13

window.a=14;
console.log(a);//=>14

//=>全局變量和window中的屬性存在「映射機制」,給變量從新賦值時,window下對應的屬性值也會發生改變;改變屬性值時,對應的變量也會被從新賦值

複製代碼

形參與arguments的映射機制

一、在JS非嚴格模式下,函數中的形參變量和arguments存在映射機制

二、arguments和形參之間的映射是以arguments的索引爲基礎完成的,arguments中有這個索引,瀏覽器會完成和對應形參變量中的映射機制搭建,若是形參比arguments中的個數多,那麼多出來的形參是沒法和arguments中的對應的索引創建關聯的

三、argument和形參的映射機制創建在函數執行後形參賦值的一瞬間,此時能創建映射機制的創建映射機制,不能創建起來的,之後無論怎麼操做都沒法再創建了

~function fn(x,y){
    var arg=arguments;
    arg[0]=100;
    console.log(x);//=>100
    console.log(arg[1]);//=>undefined
    y=200;
    console.log(arg[1]);//=>undefined
    console.log(y);//=>200
    arg[1]=300;
    console.log(y);//=>200
}(10);
複製代碼

在嚴格模式下不支持使用 arguments.callee(函數自己)/arguments.callee.caller(函數執行的suzh),並且不存在映射機制

~function(x,y){
 'use strict'
	var arg=arguments;
	arg[0]=100;
	console.log(x);//=>10
	x=200;
	console.log(arg[0]);//=>100
	console.log(arg.callee.caller)//=>Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
}(10);
複製代碼

在ES6新語法規範中能夠直接給形參設置默認值

function fn(x=0){
	//=>若是x沒有傳遞值,默認值是零,一旦傳遞值,無論傳遞的是啥,都是按照傳遞的值處理的
	console.log(x);
}
fn();//=>0
fn(null);//=>null
fn(undefined);//=>傳遞undefined,瀏覽器也是按照沒有傳遞值處理的
複製代碼

全局做用域下帶VAR和不帶VAR的區別

帶var表示聲明變量 不帶var表示給window對象下添加屬性

//=>變量提高:無

console.log(a);//=>Uncaught ReferenceError: a is not defined 因無變量提高,也沒有window.a這個屬性,因此此時a至關於一個沒有聲明過的變量,因此會出現報錯的狀況
a=12;
console.log(a);
複製代碼

console.log(a)的查找機制:先找有無聲明過的變量a,若是沒有再找有無window.a這個屬性,若是尚未仍是當作沒有聲明過的變量

語法補充

var a=12,
    b=13;

此時至關於
var a=12;
var b=13;
複製代碼
var a=b=13;

至關於
var a=13;
b=13;//<=>window.b=13
複製代碼
var n=m=[12,23];
/* * 1.開闢堆內存,存儲鍵值對 * 2.var n * 3.n=AAAFFF111 * m=AAAFFF111 / 至關於 var n=[12,23]; var m=[12,23]; 複製代碼

私有做用域中帶VAR和不帶VAR的區別

1.帶VAR的在私有做用域變量提高階段,都聲明爲私有變量,和外界沒有任何關係

2.不帶VAR不是私有變量,會向它的上級做用域查找,看是否爲上級變量,不是,繼續向上查找,一直找到window爲止(咱們把這種查找機制叫作:「做用域鏈」),也就是咱們在私有做用域中操做的這個非私有變量,是一直操道別人的

console.log(a, b);//=>undefined undefined
var a = 12;
var b=12;

function fn() {
    console.log(a, b);//=>undefined 12(這個輸出的是全局中變量b的值)
    var a = b = 13;
    /*var a=13; b=13;*/ //至關於把全局變量b被從新賦值爲13
    console.log(a, b);//=>13 13(這個輸出的是全局中變量b的值)
}

fn();
console.log(a, b);//=>12 13(在私有做用域中已經將變量b的值改變爲13)
複製代碼

擴展

function fn(){

	console.log(b)//=>Uncaught ReferenceError: b is not defined
	b=13;
	console.log('b' in window);//=>true 在做用域鏈查找的過程當中,若是找到WIN也沒有這個變量,至關於給WIN設置了一個B(window.b=13)
	console.log(b);//=>13 <=>console.log(window.b)
}
fn();
console.log(b);//=>13
複製代碼

做用域鏈查找的機制

  • 1.若是往上級查找的過程當中,上級有這個變量,那直接修改上級這個變量的值
  • 2.若是往上級查找的過程當中,一直找到window下都沒有這個變量,那麼就至關於給window這個對象添加一個屬性

練習題

var n=0;
function a(){
    var n=10;
    function b(){
        n++;
        console.log(n);
    }
    b();
    return b;
}
var c=a();
c();
console.log(n);

結果  11120
複製代碼

只對等號左邊進行變量提高

/* * 變量提高: * var fn; =>只對等號左邊進行變量提高 * sum = AAAFFF111; */

sum();
fn();//=>Uncaught TypeError: fn is not a function

var fn=function (){//=>匿名函數之函數表達式
	console.log(1);
}//=>代碼執行到此處會把函數值賦值給FN

fn();

function sum(){
	console.log(2);
}
複製代碼

給匿名函數設置名字其實沒有實際的用途,由於在函數外面是沒法獲取這個函數名的 匿名函數起的名字只能在函數體內使用

var fn=function sum() {
    console.log(sum)//=>函數體自己
};
fn();
console.log(sum)//=>Uncaught ReferenceError: sum is not defined
複製代碼
var fn=function sum() {
    console.log(sum())//=>會陷入死循環,fn執行後輸出sum執行的結果,也就是fn執行的結果,會不停的執行函數
};
fn();

複製代碼

條件判斷下的變量提高

在當前做用域下,無論條件是否成立都要進行變量提高

  • 帶VAR的仍是隻聲明
  • 帶function的在老版本瀏覽器渲染機制下,聲明和定義都處理,可是爲了迎合ES6中的塊級做用域,新版瀏覽器對於(在條件判斷中的函數),無論條件是否成立,都只是先聲明,沒有定義,相似於VAR
/* * 變量提高 * 無 * / console.log(a);//=>undefined 變量a只聲明未定義 if("a" in window){ var a=100; } console.log(a);//=>100 變量a的值爲100 複製代碼

老版本瀏覽器下

無論條件是否成立,都會進行變量提高

  • 帶VAR的只聲明
  • 帶FUNCTION的聲明並定義
/
 * 變量提高:無
/

f=function(){return true;};//=>發生修改變爲f=....false
g=function(){return false;};
~function(){
//變量提高 g=AAAFFF111
	if(g() && [] == ![]){//=>此時函數g已經聲明及定義了,是私有變量,因此執行函數g()的結果是true
		f=function(){return false};//=>非私有變量,往上找,至關於改了window.f=....false,
		function g(){return true};
	}
}()
console.log(f());//=>false
//至關於自執行函數:(function(){return false;})();,把該函數執行,返回值爲false
console.log(g());//=>false
//至關於自執行函數:(function(){return false;})();,把該函數執行,返回值爲false
複製代碼

新版瀏覽器下

無論條件是否成立,都進行變量提高

  • 帶VAR的只聲明
  • 帶FUNCTION的只聲明
f=function(){return true;};
g=function(){return false;};
~function(){
	//=>變量提高:只聲明瞭函數g,未定義(至關於var g;)
	if(g() && [] == ![]){//=>Uncaught TypeError: g is not a function 此時的g是undefined)
		f=function(){return false};
		function g(){return true};
	}
}()
console.log(f());
console.log(g());
複製代碼

特殊狀況

條件中的function在變量提高階段也是隻聲明,當代碼執行,若是條件成立,進入條件後的第一件事情,就是把函數定義了,而後再執行判斷體中的代碼

console.log(a,fn);//=>undefined undefined

if(1===1){
	console.log(a,fn)//=>undefined fn函數自己
	//=>當條件成立,進入到判斷體中(在ES6中它是一個塊級做用域)第一件事情並非代碼執行,而是相似於變量提高同樣,先把定義了(最開始的時候已經聲明瞭),也就是判斷體中代碼執行以前,FN就已經賦值了

	var a=12;
	function fn(){
		console.log('ok');
	}
}

console.log(a,fn);//=> 12 fn函數自己
複製代碼

重名問題的處理

帶VAR和FUNCTION關鍵字聲明相同的名字,這種也算是重名了(實際上是一個FN,只是存儲值的類型不同)

關於重名的處理:若是名字重複了,不會從新的聲明,可是會從新的定義(從新賦值)[無論是變量提高仍是代碼執行階段皆是如此]

/
* 變量提高:
* fn=aaafff111
*   =aaafff222
*   =aaafff333
*   =aaafff444
/
fn();//=>4
function fn(){console.log(1);}
fn();//=>4
function fn(){console.log(2);}
fn();//=>4
var fn =100;
fn();//<=>100() Uncaught TypeError: fn is not a function
function fn(){console.log(3);}
fn();
function fn(){console.log(4);}
fn();
複製代碼

ES6

ES6變量

在ES6中基於let/const等方式建立變量或者函數,不存在變量提高機制

切斷了全局變量和WINDOW屬性的映射機制,可是ES6的全局做用域下也是有WINDOW屬性,只是沒有了映射機制

不帶let或者const就和ES6沒有關係

基於LET和基於VAR建立變量,在私有變量和私有做用域鏈機制上是同樣的

如今項目中ES5 && ES6混合開發模式,若是當前變量是基於LET建立的,就按照ES6的新語法機制渲染,不然按照ES5的老語法機制渲染

console.log(a);//=>Uncaught ReferenceError: a is not defined
let a=12;
console.log(window.a);//=>undefined
console.log('a' in window)//=>false
console.log(a);//=>12
複製代碼

在相同做用域中,基於LET不能聲明相同名字的變量(無論用什麼方式在當前做用域下聲明瞭變量(例如var,或者設置形參),再次使用let建立都會報錯)

let a=12;
cosole.log(a);
let a=13;//=>Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);

//代碼不會執行,直接報錯
複製代碼
console.log(a);
var a=12;
console.log(a);
let a=13;//=>Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);

//代碼不會執行,直接報錯
複製代碼
let fn=(a)=>{//=>ES6箭頭函數
	let a=b=c=100;
};
fn();//=>3-LET.js:22 Uncaught SyntaxError: Identifier 'a' has already been declared
複製代碼

雖然沒有變量提高機制,可是在當前做用域代碼自上而下執行以前,瀏覽器會作一個重複性檢測:自上而下查找當前做用域下全部變量,一旦發現有重複的,直接拋出異常,代碼也不會在執行了(雖然沒有把變量提早聲明定義,瀏覽器已經記住了,當前做用於域有哪些變量)

b=12;
console.log(b);//=>12
console.log(window.b);//=>12
a=12;//=>Uncaught ReferenceError: a is not defined
//=>代碼執行以前,瀏覽器會記住全部ES6下聲明的變量而且記住,此時代碼開始執行,執行到這一步的時候,瀏覽器知道有這個變量,可是尚未聲明,因此出現a is not defined的錯誤
console.log(a);
let a=13;
console.log(a);
複製代碼
let a=10,
    b=10;
let fn=function (){
	console.log(a.b)//=>Uncaught ReferenceError: a is not defined
	//=>在當前私有做用域下,瀏覽器會先記住該做用域下會聲明一個a,輸出a的時候啊尚未被聲明,因此會報錯a is not defined
	let a=b=20;//<=>let a=20; b=20;(把全局中的b改成20)
	console.log(a,b)
};
//=>箭頭函數,也能夠這樣寫 let fn=(a)=>{}
fn();
console.log(a,b);
複製代碼

ES6的暫時性死區

在原有瀏覽器渲染機制下,基於typeof等邏輯運算符檢測一個未被聲明過的變量,不會報錯,返回undefined

若是當前變量是基於ES6語法,在沒有聲明這個變量的時候,使用typeof檢測會直接報錯,不會是undefined,解決了原有的JS死區

//=>ES5下
console.log(typeof a);//=>'undefined' 

------------------------------
//=>ES6下
console.log(typeof a);//Uncaught ReferenceError:a is not defined
複製代碼

ES6箭頭函數

語法

let fn=(x,y)=>{};
fn(10,20);
複製代碼

只有一個形參,咱們能夠省略小括號

let fn=x=>{};
fn(10);
複製代碼

若是函數體中只有一句操做,而且是return的,咱們能夠省略大括號

let fn=function (x,y){return x+y;};
let fn =(x=0,y=0)=>x+y;
複製代碼
let fn=x=>y=>x+y;
---------------
var fn=function fn(x){
	return function(y){
		return x+y;
	};
};
複製代碼

箭頭函數中沒有arguments

let fn=(...arg)=>{
	console.log(arguments);//=>Uncaught ReferenceError: arguments is not defined
	console.log(arg);//=>可使用剩餘運算符代替,並且arg是一個數組
};
fn(10,20,30,40);
複製代碼

箭頭函數中沒有本身的執行主體(this),它的this都是默認繼承上下文中的this,即便經過call方法改變this指向也是沒用的

let obj={
	fn:(function (){//=>自執行函數中的this是window
		return function(){
			console.log(this);
		}
	})()
};
obj.fn();//=>this:obj
//=>如何讓obj.fn()執行輸出的this是window
複製代碼

第一種解決方案

let obj={
	fn:(function (){
		let _this=this;
		return function (){
			console.log(_this);//=>this只是一個變量,不是私有的,找上級做用域中的
		}
	})()
};
複製代碼

第二種解決方案

let obj={
	fn:(function (){
		return ()=>{
			console.log(this);
		}
	})()
}
//=>箭頭函數中的this和執行前面有沒有點沒有關係,箭頭函數中沒有this,使用到的this都是直接找上下文中的this來使用
//=>暫時理解爲找到上級做用域中的this
複製代碼

私有變量

在私有做用域中,只有如下兩種狀況是私有變量

  • A:聲明過的變量(帶VAR/FUNCTION/LET)
  • B:形參也是私有變量

函數執行的步驟

  • 一、形參賦值
  • 二、變量提高
  • 三、代碼自上而下執行
  • 四、當前棧內存(私有做用域)銷燬或者不銷燬
/
* 變量提高  
* var a; var b; var c;
/
var a=12,
    b=13,
    c=14;

function fn(a){
	/
	* 1.形參賦值  a=12
	* 2.變量提高  var b;
	/
	console.log(a,b,c)//=>(12,undefined,14)
	var b=c=a=20;
	/
	* var b=20;//=>私有變量b賦值爲20
	* c=20;//=>把全局中的c修改成20
	* a=20;//=>私有變量a修改成20
	/
	console.log(a,b,c)//=>(20,20,20)
}
fn(a);//=>把FN執行(小括號中是實參:值) =>執行FN把全局變量A的值12當作實參傳遞給函數的形參 =>fn(12)
console.log(a,b,c);//=>(12,13,20)
複製代碼

練習

var ary=[12,23];
function fn(ary){
	console.log(ary);
	ary[0]=100;
	ary=[100];
	ary[0]=0;
	console.log(ary);
}
fn(ary);
console.log(ary);
複製代碼

查找上級做用域

當前函數執行,造成一個私有做用域A,A的上級做用域是誰,和他在哪執行的沒有關係,只和他在哪建立(定義)的有關係,在哪建立的,它的上級做用域就是誰

在傳統ES5中,只有全局做用域和函數執行造成的私有做用域這兩種做用域,循環或者判斷等都不是做用域

arguments:實參集合 arguments.callee:函數自己FN arguments.callee.caller:當前函數在哪執行的,caller就是誰(記錄的是它執行的宿主環境),在全局下執行的結果是null

var a=12;

function fn(){
	console.log(arguments.callee.caller)//=>sum這個函數自己
}

function sum(){
	var a=120;
	fn();
}
複製代碼

練習

var n=10;
function fn(){
	var n=20;
	function f(){
		n++;
		console.log(n);
	}
	f();
	return f;
}
var x=fn();
x();
x();
console.log(n);
複製代碼

JS中的內存分爲堆內存和棧內存

堆內存

存儲引用數據類型值(對象:鍵值對 函數:代碼字符串)

堆內存釋放

讓全部引用堆內存空間地址的變量賦值爲null便可(沒有變量佔用這個堆內存了瀏覽器會在空閒的時候把它釋放掉)

棧內存

提供JS代碼執行的環境和存儲基本類型值

棧內存釋放

通常狀況下,當函數執行完成,所造成的私有做用域(棧內存)都會自動釋放掉(在棧內存中存儲的值也都會釋放保證瀏覽器的高速運轉),可是也有特殊不銷燬的狀況:

  • 1.函數執行完成,當前造成的棧內存中,某些內容被棧內存之外的變量佔用了,此時棧內存不能釋放(一旦釋放外面 找不到原有的內容了)
  • 2.全局棧內存只有在頁面關閉的時候纔會被釋放掉

若是當前棧內存沒有被釋放,那麼以前在棧內存中存儲的基本值也不會被釋放可以一直保存下來

知識點補充

i++:自身累加1(先拿原有值計算,運算結束後,自己累加1)
++i:自身累加1(先自身累加1,拿累加後的結果進行運算)

var k=1;
console.log(5+(++k),k);//=>(7,2)
console.log(5+(k++),k);//=>(6,2)


-------------
var k=1;
console.log(5 + (++k) + (k++) + 4 + (k--) + (++k) + 3 + (--k) + (k++), k);//=>(26,3)
複製代碼

return後面返回的都是值,因此無論後面跟的是什麼都不會進行變量提高

var i = 1;
function fn(i) {
    return function (n) {
        console.log(n + (++i));
    }
}
var f = fn(2);
f(3);
fn(5)(6);
fn(7)(8);
f(4);

複製代碼

var i = 2;
function fn() {
    i += 2;
    return function (n) {
        console.log(n + (--i));
    }
}
var f=fn();
f(2);
f(3);
fn()(2);
fn()(3);
f(4);
複製代碼

let i=1;
let fn=function(n){
	i*=2;
	return function (m){
		i+=n+m;
		console.log(i);
	}
};
let f=fn(2);
f(3);//=>7
fn(2)(3);//=>19
f(4);//=>25
f(5);//=>32
複製代碼

閉包

定義

函數執行造成一個私有的做用域,保護裏面的私有變量不受外界的干擾,這種保護機制稱之爲"閉包" 部分開發者認爲閉包是:造成一個不銷燬的私有做用域(私有棧內存)纔是閉包

//=>閉包:柯理化函數
function fn(){
	return function (){
	
	}
}
var f=fn();
------------------------------

//=>閉包:惰性函數
var utils=(function(){
	return {
	
	}
})();
複製代碼

閉包實戰應用

真實項目中爲了保證JS的性能(堆棧內存的性能優化),應該儘量的減小閉包的使用(不銷燬的堆棧內存是消耗性能的)

1、閉包的保護做用

保護私有變量不受外界的干擾

在真實項目中,尤爲是團隊協做開發的時候,應當儘量的減小全局變量的使用,以防止相互之間的衝突("全局變量污染"),那麼此時咱們徹底能夠把本身這一部份內容封裝到一個閉包中,讓全局變量轉化爲私有變量

(function(){
	var n=12;
	function fn(){
	
	}
})();
複製代碼

不只如此咱們封裝類庫插件的時候,也會把本身的程序都存放到閉包中保護起來,防止和用戶的程序衝突,可是咱們又須要暴露一些方法給客戶使用,這樣咱們如何處理呢?

1.JQ這種方式,把須要暴露的方法拋到全局

(function(){
	function jQuery(){
		...
	}
	window.jQuery=window.$=jQuery//=>把須要供外面使用的方法,經過給window設置屬性的方式暴露出去
})()
jQuery();
$();
複製代碼

2.Zepto這種方式:基於RETURN把須要供外面使用的方法暴露出去

var Zepto=(function(){
	//...
	return {
		xxx:function(){
		
		}
	};
})();
Zepto.xxx()
//=>Zepto的值是自執行函數執行後返回的值,因此是return後面的對象,至關於Zepto={xxx:function(){}}
//Zepto.xxx,至關於等於xxx屬性名對應的屬性值
複製代碼
2、閉包的保存做用

造成不銷燬的棧內存,把一些值保存下來,方便後面的調取使用

案例:選項卡

var oTab=document.getELementById('tab'),
	tabList=oTab.getElementsByTagName('li'),
	divList=oTab.getElementsByTagName('div');

function changeTab(curIndex){
	for(var i=0;i<tabList.length;i++){
		tabList[i].className="";
		divList[i].className="";
	}
	tabList[curIndex].className="active";
	divList[curIndex].className="active"
}
複製代碼

一、不能夠直接使用索引i的緣由

事件綁定是"異步編程",當觸發點擊行爲,綁定的方法執行的時候,外層循環已經結束;方法執行產生私有做用域,用到變量i,不是私有的變量,按照"做用域鏈"的查找機制,找到的是全局下的i(此時全局的i已經成爲循環最後一次的結果3)

for(var i=0;i<tabList.length;i++){
	tabList[i].onclick=function{
		changeTab(i);
	}
}
複製代碼

1.想要觸發事件以前,首先頁面需加載完畢,JS代碼也已經加載完成,也就是該循環已經結束,此時的i變爲了3

2.當某個li被點擊的時候會觸發事件,執行函數,函數執行時會造成一個私有做用域,此時i不是私有變量,基於做用域鏈機制,會往上級做用域(也就是window做用域)查找,此時的i已經變爲3 3.全部的事件綁定都是異步編程

  • 同步編程:一件事一件事的作,當前這件事沒完成,下一個任務不能處理
  • 異步編程:當前這個事件沒有完全完成,再也不等待,繼續執行下面的任務
    • 綁定事件後,不須要等待執行,繼續執行下一個循環任務,因此當咱們點擊執行方法的時候,循環早已結束(讓全局的i等於循環最後的結果3)

二、使用閉包實現

利用閉包的機制,把後期須要的索引實現存儲到本身的私有做用域中:"閉包有保存做用" 第一種

for(var i=0;i<tabList.length;i++){
	tabList[i].onclick=(function(n){
	//=>讓自執行函數執行,把執行的返回值return賦值給onlick事件(此處onclick綁定的是返回的小函數,點擊的時候執行的是小函數),自執行函數在給事件賦值的時候就已經執行了
	
		var i=n;
		return function(){
			changeTab(i);//=>上級做用域:自執行函數造成的做用域
		}
	})(i);
}
複製代碼

循環三次,造成三個不銷燬的私有做用域(自執行函數執行),而每個不銷燬的棧內存中存儲了一個私有變量i,這個值分別是每一次執行傳遞進來的全局i的值(也就是:第一個不銷燬的做用域存儲的是0,第二個是1,第三個是2);當點擊的時候,執行返回的小函數,遇到變量i,向它本身的上級做用域查找,找到的i值分別是:0/1/2,達到了咱們想要的效果

var btnBox = document.getElementById('btnBox'),
    inputs = btnBox.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
    inputs[i].onclick = (function (i) {
        return function () {
            alert(i);
        }
    })(i);//=>把每一次循環時候i(全局的)的值傳遞給自執行函數
}
複製代碼

第二種

for(var i+0;i<tabList.length;i++){
	(function(n){
		tabList[n].onclick=function(){
			changeTab(n);
		}
	})(i)
}
//=>原理都是造成三個不銷燬的私有做用域,分別存儲須要的索引值
複製代碼

三、使用ES6方法實現

ES6知識點補充

基於ES6中的LET來建立的變量是存在塊級做用域的(相似於私有做用域,有私有變量和做用域鏈查找機制),在大括號裏面也是從新檢測語法規範,看一下是不是基於新語法建立的變量,若是是,按照新語法規範來解析

ES6中的做用域

  • 1.全局做用域
  • 2.私有做用域(函數執行)
  • 3.塊級做用域(通常用大括號包起來的都是塊級做用域,對象除外
let a=100;
{
	let a=200;
	{
		let a=300;
		{
			console.log(a);//=>300
		}
	}
}
//=>每一個變量a是不同的

-------------------------------

let a=100;
{
	let a=200;
	{
		a=300;//=>把上級塊級做用域中的a從200改成300;
		{
			console.log(a);//=>300,先往上級塊級做用域找,沒有變量a,再往上找有變量a了可是已經變成300了
		}
	}
}

---------------------------
let a=100;
{
	let a=200;
	{
		{
			console.log(a);//=>Uncaught ReferenceError: a is not defined
			//=>當前塊級做用域沒有變量a,須要往上級塊級做用域找,執行到當前代碼的時候,尚未聲明定義a,可是瀏覽器知道有a這個變量,因此會報錯a is not defined
		}
		let a=300;
	}
}
複製代碼
var a=12;
if(true){
	console.log(a);//=>Uncaught ReferenceError: a is not defined
	let a=13;
	let b=12;
}
console.log(b)//=>Uncaught ReferenceError: b is not defined
//=>由於if裏面是用let建立的因此是塊級做用域,a和b至關於該塊級做用域的私有變量,和外面是沒有關係的
複製代碼
var a=12;
if(true){
	console.log(a);//=>Uncaught ReferenceError: a is not defined
	let a=13;
	var b=12;
}
console.log(b)//=>12

複製代碼
if(1===1){
	let fn=function (){
		console.log(1);
	};
	console.log(fn);//=>fn是當前判斷體塊級做用域中私有的
}
console.log(fn);//=>Uncaught ReferenceError: fn is not defined 
複製代碼
var a=12;
if(true){
	console.log(a);//=>Uncaught ReferenceError: a is not defined
	let a=13;
	b=12;//=>至關於給全局做用域下加了一個window.b的屬性
}
console.log(b)//=>12
console.log("b" in window)//=>true
複製代碼

解決選項卡

ES6和閉包的機制相似,ES6中使用LET建立變量,會造成塊級做用域

for(let i=0;i<5;i++){
	//=>循環體也是塊級做用域,初始值設置的變量是當前塊級做用域中的變量(造成了五個塊級做用域,每一個塊級做用域中都有一個私有變量i,變量值就是每一次循環i的值)
	
}
console.log(i)//=>Uncaught ReferenceError: i is not defined
複製代碼
for(let i=0;i<tabList.length;i++){
	tabList[i].onclick=function(){
		changeTab(i);
	}
}

第一次循環
{
	let i=0;
	{
		tabList[i].onclick=function(){//=>i往上級塊級做用域找,i=0
			changeTab(i);
		}
	}
}

第二次循環
{
	let i=1;
	{
		tabList[i].onclick=function(){i往上級塊級做用域找,i=1
			changeTab(i)
		}
	}
}

第三次循環
{
	let i=2;
	{
		tabList[i].onclick=function(){i往上級塊級做用域找,i=2
			changeTab(i)
		}
	}
}

第四次循環
{
	let i=3;
	{
		tabList[i].onclick=function(){i往上級塊級做用域找,i=3
			changeTab(i)
		}
	}
}

第五次循環
{
	let i=4;
	{
		tabList[i].onclick=function(){i往上級塊級做用域找,i=4
			changeTab(i)
		}
	}
}

//=>每一次循環都造成一個塊級做用域,並且塊級做用域內都存儲了當前循環的i的值
複製代碼

單例

單例設計模式(singleton )

表現形式

var obj={
	xxx:xxx,
	....
};

複製代碼

在單例設計模型中,OBJ不只僅是對象名,它被稱爲"命名空間[NameSpace]",把描述事物的屬性存放到命名空間中,多個命名空間是獨立分開的,互不衝突

做用

把描述同一件事物的屬性和特徵進行"分組、歸類"(存儲在同一個堆內存空間中),所以避免了全局變量之間的衝突和污染

var parttern1={name:'xxx'};
var parttern2={name:'xxx'};
複製代碼

命名由來

每一個命名空間都是JS中Object這個內置基類的實例,而實例之間是相互獨立互不干擾的,因此咱們稱它爲"單例:單獨的實例"

高級單例模式

在給命名空間賦值的時候,不是直接賦值給一個對象,而是先執行自執行函數,造成一個私有做用域AA(不銷燬的棧內存),在AA中建立一個堆內存,把堆內存地址賦值給命名空間(惰性思想

這種模式的好處:咱們徹底能夠在AA中創造不少內容(變量or函數),哪些須要供外面調取使用的,咱們暴露到返回的對象當中(模塊化實現的一種思想)

var nameSpace=(function(){
	var n=12;
	function fn(){
	}
	function sum(){
	}
	return {
		fn:fn,
		sum:sum
	}
})();
nameSpace.fn();//=>至關於在外面調取fn這個函數並執行,nameSpace.fn是fn這個屬性名對應的屬性值,也就是fn
//=>nameSpace的值爲自執行函數執行返回的值,也就是return後面的對象
複製代碼

this

當前方法執行的主體(誰執行的這個方法,那麼this就是誰,因此this和當前方法在哪建立的或者在哪執行的都沒有必然聯繫)

非嚴格模式下

第一種狀況

給當前元素的某個事件綁定方法,當事件觸發方法執行的時候,方法中的this是當前操做的元素對象

oBox.onclick=function(){
	//=>this:oBox
}
複製代碼

第二種狀況

普通函數執行,函數中的this取決於執行的主體。誰執行的,this就是誰(執行主體:方法執行,看方法名前面是否有,有的話,前面是誰this就是誰,沒有的話,this就是window)

function fn(){//=>AAAFFF000
	console.log(1);
}
var obj={
	fn:fn//=>fn:AAAFFF000
}

//=>執行的是相同的方法(不一樣的地方在於函數執行方法中的this是不同的)
obj.fn();//=>this:obj
fn();//=>this:window

複製代碼

案例一

var name="window";
var Tom={
	name:"Tom",
	show:function(){
		console.log(this.name);
	},
	wait:function(){
		var fun=this.show;//=>此時的this是Tom
		fun();//=>前面沒有點,至關於直接執行該函數,此函數的this是window
	}
};
Tom.wait();//=>window
Tom.show();//=>Tom
複製代碼
var length=10;
	fuction fn(){
		console.log(this);
		console.log(this.length);
	}
	var obj={
		length:5;
		method:function(fn){
			fn();
			arguments[0]();
		}
	};
console.log(obj.method(fn,1));
window 10
arguments自己  2
複製代碼

第三種狀況

自執行函數,通常狀況下this是window

~function(){
	//=>this:window
}();
複製代碼

練習題

var n = 2;
var obj={
    n:3,
    fn:(function (n) {
        n*=2;
        this.n+=2;
        var n=5;
        return function (m) {
            this.n*=2;
            console.log(m + (++n));
        }
    })(obj.n)//=>Uncaught TypeError: Cannot read property 'n' of undefined
};
var fn = obj.fn;
fn(3);
obj.fn(3);
console.log(n, obj.n);

報錯的緣由:
obj處是先開闢一個堆內存存儲鍵值對,fn對應的屬性值是自執行函數執行後返回的結果,而此時鍵值對存儲尚未完成,也就是尚未和變量obj關聯在一塊兒,obj至關於undefined(obj在全局做用域下只聲明未定義),undefined是基本數據類型沒有屬性,因此會報錯
複製代碼
var n = 2;
var obj={
    n:3,
    fn:(function (n) {
        n*=2;
        this.n+=2;
        var n=5;
        return function (m) {
            this.n*=2;
            console.log(m + (++n));
        }
    })(n)
};
var fn = obj.fn;
fn(3);//=>this:window 9
obj.fn(3);//=>this:obj 10
console.log(n, obj.n);//=>(8,6)
複製代碼

var num=1,
    obj={
	    num:2,
	    fn:(function(num){
		    this.num*=2;
		    num+=2;
		    return function (){
			    this.num*=3;
			    num++;
			    console.log(num);
		    }
	    })(num)
    };
var fn =obj.fn;
fn();//=>4
obj.fn();//=>5
console.log(num,obj.num);//=>(6,6)
    
複製代碼

第四種狀況

構造函數執行,方法體中的this是當前類的一個實例

function Fn(){
	this.name="zhufeng";
	this.age=18;
}
var f=new Fn();
console.log(f);//{name:"zhufeng",age:18}
複製代碼

第五種狀況

箭頭函數中沒有本身的this,this是上下文中的this

let obj={
	fn:function(){
		//=>this:obj
		setTimeout(()=>{
			//=>this:obj,繼承了fn裏面的this
		},1000);
	}
}
複製代碼

第六種狀況

小括號表達式中,會影響this的指向

let obj={
	fn:function(){
		console.log(this);
	}
};
obj.fn();//=>this:obj
(12,obj.fn)();//=>this:window
複製代碼

第七種狀況

使用call/apply/bind能夠改變this指向

fn.call(obj);//=>this:obj

fn.call(12);//=>this:12

fn.call();//=>this:window
複製代碼

非嚴格模式下call/apply/bind第一個參數不寫或者寫null和undefined,this都是window,嚴格模式下寫誰this就是誰,不寫是undefined

嚴格模式

開啓嚴格模式:在當前做用域的第一行加上'use strict'(快捷鍵 us+[TAB]),那麼當前做用域下再執行的JS代碼都是按照嚴格模式處理的

'use strict';
//=>當前JS代碼都開啓了嚴格模式(包含了函數中的代碼)
複製代碼
~function(){
 'use strict';
	//=>只是把當前私有做用域開啓了嚴格模式(對外面全局沒有影響)
}();
複製代碼

第一種狀況

在JS嚴格模式下,若是執行主體不明確,this指向的是undefined(非嚴格模式下指向的是window)

function fn(){
	console.log(this);
}
fn();//=>window
window.fn();//=>window
複製代碼
"use strict";
function fn(){
	console.log(this);
}
fn();/=>undefined
window.fn();//=>window
複製代碼

第二種狀況

自執行函數中的this爲undefined

function fn(){
	console.log(this);//=>window
}
document.body.onclick=function (){
	console.log(this)//=>document.body
	fn();
}
複製代碼

單例設計模塊化開發

1.團隊協做開發的時候,會把產品按照功能板塊進行劃分,每個功能板塊有專人負責開發

//=>搜索板塊
var searchModel={
	submit:function(){
		untils.aa();
		//=>能夠直接調取公共板塊的方法
	}
}

//=>頻道板塊
var channelModel={
	setChannel:function(){}
	translateChannel:function(){}
	show:function(){
		secrchModel.submit();
		//=>在當前的命名空間下調取其它命名空間的方法:制定好對應的空間名字便可,使用[nameSpace].[property]就能夠操做
		this.setChannel();
		//=>調取本模塊中的一些方法,能夠直接使用this處理便可:此方法中的this通常都是當前模塊的命名空間
	}		
}
channelModel.show();//=>表明調取channelModel下的show方法

//=>天氣板塊
var weatherRender=(function(){
	var fn=function(){};
	var f=function(){};
	//=>想暴露在外面的方法就寫在return裏面
	return {
		init:function(){
			fn();//=>調取本身板塊中的方法直接調取使用便可
			channelModel.show();
		},
		fn:fn,
		aa:channnelModel.show
	}
})();
weatherRender.init();
複製代碼

2.把各個板塊之間公用的部分進行提取封裝,後期再想實現這些功能,直接的調取引用便可

//=>公共板塊
var utils=(function(){
	return {
		aa:function(){
		}
	}
})();

//=>搜索模塊
var searchModel={
	submit:function(){
		untils.aa();
		//=>能夠直接調取公共板塊的方法
	}
}
複製代碼

``

工廠模式(Factory Pattern)

1.把實現相同功能的代碼進行"封裝",以此來實現"批量生產"(後期想要實現這個功能,咱們只須要執行函數便可) 2."低耦合高內聚":減小頁面中的冗餘代碼,提升代碼的重複使用率

function createPerson(name,age){
	var obj={};
	obj.name=name;
	obj.age=age;
	return obj;
}
var p1=createPerson('xxx',25);
var p2=createPerson('xxx',25);
複製代碼

面向對象(OOP)

JS是一門編程語言(具有編程思想)

  • [面向對象]
    • JS\JAVA\PHP\C#\Ruby\Python\C++
  • [面向過程]
    • C

對象、類、實例

對象:萬物皆對象(咱們所要研究學習以及使用的都是對象)

:對象的具體細分(按照功能特色進行分類:大類、小類)

實例:類中具體的一個事物(拿出類別中的具體一個實例進行研究,那麼當前類別下的其它實例也具有這些特色和特徵)

整個JS就是基於面向對象設計和開發出來的語言,咱們學習和實戰的時候也要按照面向對象的思想去體會和理解

JS中的內置類

  • Object:對象類(基類),每個對象都是它的一個實例
    • Number:每個數字或者NaN是它的一個實例
    • String:字符串類
    • Boolean:布爾類
    • Null:瀏覽器屏蔽了操做Null這個類
    • Undefined:瀏覽器屏蔽了操做Undefined這個類
    • Array:數組類
    • RegExp:正則類
    • Date:日期類
    • Function:函數類
    • HTMLCollection:元素集合類
    • NodeList:節點集合類
    • EventTarget

爲何getElmentById只能經過document獲取?

由於getElementById這個方法只有Document這個類纔有,其它類沒有,document是Document這個類的一個實例,Document這個類同時也提供getElementsByTagName/getElementsByClassName等方法。

頁面中的其餘元素都是Element類的一個實例,Element只提供getElementsByTagName/getElementsByClassName等方法,沒有getElementById這個方法。

constructor(構造函數模式)

建立某個類的一個實例的方法

var a=new 類名();

var num=new Number(1);
//=>num是Number類的一個實例,值爲1

---------------------------------
var ary=new Array();
//=>ary是Array類的一個實例
1.new Array(10):建立一個長度爲10的數組,數組中的每一項都是空
2.new Array("10"):若是隻傳遞一個實參,而且實參不是數字,至關於把當前值做爲數組的第一項存儲進來
3.new Array(10,20,30):若是傳遞多個實參,不是設置長度,而是把傳遞的內容當作數組中的每一項存儲起來

------------------
var obj=new Object();
//=>通常只用於建立空對象,若是須要增長鍵值對,建立完成後本身依次添加("對象.屬性名"或者"對象[屬性名]"的方式)
複製代碼

基於構造函數建立自定義類

一、在普通函數執行的基礎上"new xxx()",這樣就不是普通函數執行了,而是構造函數執行,當前的函數名稱之爲"類名",接收的返回結果是當前類的一個實例 二、本身建立的類名,最好第一個單詞首字母大寫 三、這種構造函數設計模式執行,主要用於組件、類庫、插件、框架等的封裝,平時編寫業務邏輯通常不這樣處理

JS中引用數據類型建立值的兩種方式

無論是哪種方式創造出來的都是Object類的實例,而實例之間是獨立分開的,因此 var xxx={},這種模式就是JS中的單例模式

一、字面量表達式

var obj ={};//=>這種模式是JS的單例模式
複製代碼

二、構造函數模式

var obj = new Object();
複製代碼

JS中基本數據類型建立值的兩種方式

一、基於字面量方式建立出來的值是基本類型值 二、基於構造函數建立出來的值是引用類型(對應的實例都是對象類型的)

var num1=12;
var num2=new Number(12);
console.log(typeof num1);//=>"number"
console.log(typeof num2);//=>"object"

//=>num1是數字類的實例,num2也是數字類的實例,num2只是JS表達數字的方式之一,均可以使用數字類提供的屬性和方法
複製代碼

構造函數執行的步驟

一、造成私有做用域(棧內存)

  • 形參賦值
  • 變量提高

二、【構造函數執行獨有】在JS代碼自上而下執行以前,首先在當前造成的私有棧內存中建立一個對象(建立一個堆內存:暫時不存儲任何的東西),而且讓函數中的執行主體(THIS)指向這個新的堆內存

三、代碼自上而下執行 ,this.xxx=xxx這類操做都是在給建立的這個對象(this)增長屬性名和屬性值

四、【構造函數獨有】代碼執行完成,把以前建立的堆內存地址返回(瀏覽器默認返回),那麼開始建立的對象其實就是當前這個類的一個實例。

function Fn(){
	var n=10;
	this.name=name;
	this.age=age+n;
}
var f=new Fn();//=>在構造函數執行的時候,若是Fn不須要傳遞實參,咱們能夠省略小括號,意思仍是建立實例(和加小括號沒有區別)
複製代碼

若是構造函數中,寫了RETURN

1.return後面的代碼都不執行

2.return後面什麼也不寫,返回的仍是this指向的那個對象了

3.return後面是一個基本數據類型值,返回的結果依然是this指向的對象

4.return後面是一個引用數據類型值,則會把默認返回的實例覆蓋,此時接收到的結果就不在是當前類的實例了

function Fn(){
	var n=10;
	this.m=n;
	return [12,23];
	this.a=20;
}
var f=new Fn();
console.log(f)//=>[12,23]

------------------
function Fn(){
	var n=10;
	this.m=n;
	return "珠峯";
	this.a=20;
}
var f=new Fn();
console.log(f)//=> f={m:10}

複製代碼

檢測方法補充

instanceof

檢測某一個實例是否隸屬於這個類(不能檢測基本數據類型只能檢測對象類型)

in

檢測當前對象是否存在某個屬性(無論當前這個屬性是對象的私有屬性仍是公有屬性,只要有結果就是TRUE)

in能夠檢測到本身添加的屬性和瀏覽器內置的私有屬性以及原型上的內置的屬性及手動添加的屬性

let obj={name:"zhufeng",age:18};
"toString" in obj;//=>true

必定要注意添加雙引號,屬性名都是字符串或者數字,不加,瀏覽器會當作變量來處理
複製代碼

hasOwnProperty

檢測當前屬性是否爲對象的私有屬性(不只要有這個屬性,並且必須仍是私有的才能夠)

瀏覽器內置的那些屬性是不可枚舉的,本身手動添加的是可枚舉的

hasOwnProperty只能找到可枚舉的私有屬性

特殊狀況:JS的類狀圖,除了第一排的那些類以及window,Winow(例如:Array,Number,Function,EvenTarget),使用hasOwnProperty·能夠檢測到瀏覽器內置的私有屬性,其餘是不行的

let box=document.getElementById("tabBox");
box.aa=200;
box.hasOwnProperty("className");//=>flase
box.hasOwnProperty("aa");//=>true


//className是box元素對象的內置屬性

//aa是咱們本身手動給box元素對象添加的內置屬性
-------------------
let obj={name:"zhufeng",age:9};
obj.hasOwnProperty("name");//=>true
obj.hasOwnProperty("toString");//=>false
//=>toString是原型上的公有屬性
複製代碼

練習:封裝hasPubProperty方法

function hasPubProperty(obj,attr){
	while(attr in obj){
		if(obj.hasOwnProperty(attr)){
			return false;
		}
		return true;
	}
	return false;
}
複製代碼

原型和原型鏈

函數和對象的分類

[函數] 普通函數、(全部的類:內置類、本身建立的類、Date)

[對象] 普通對象、數組、正則、Math、arguments... 實例是對象類型的(除了基本數據類型的字面量建立的值) prototype的值也是對象類型的 函數也是對象類型的

原型

一、全部的函數數據類型都天生自帶一個屬性:prototype(原型),這個屬性的值是一個對象,這個對象中存儲了當前類供其實例調取使用的公有屬性和方法,瀏覽器會默認給它(prototype)開闢一個堆內存

二、在瀏覽器給prototype開闢的堆內存中有一個天生自帶的屬性:constructor,這個屬性存儲的值是當前函數自己

三、每個對象都有一個_proto_(原型鏈)的屬性,這個屬性指向當前實例所屬類的prtototype(若是不能肯定它是誰的實例,都是Object的實例)

全部函數數據類型都天生自帶name和length兩個屬性,name存儲的是該函數的函數名(字符串),length存儲的是形參的數量

function fn(x,y,z){
    console.log(fn.name);//=>"fn"
    console.log(fn.length);//=>3
}
fn(10);
--------------
var f=function (x,y,z){
    console.log(f.name);//=>"f"
    console.log(fn.length);//=>3
};
f(10);

------------------
function fn(){
    return function(x,y,z){

    }
}
var a=fn();
console.dir(a);
//=>name:""
//=>length:3
複製代碼
function Fn(){
	var n=100;
	this.AA=function(){
		console.log("AA[私]")
	};
	this.BB=function(){
		console.log("BB[私]")
	};
}
Fn.prototype.AA=function(){
	console.log("AA[公]")
};

var f1=new Fn;
var f2=new Fn;
複製代碼

原型鏈

它是一種基於__proto__向上查找的機制。當咱們操做實例的某個屬性或者方法的時候,首先找本身空間中私有的屬性或者方法

1.找到了,則結束查找,使用本身私有的便可

2.沒有找到,則基於__proto__找所屬類的prototype,若是找到就用這個公有的,若是沒有找到,基於原型上的__proto__繼續向上查找,一直找到Object.prototype的原型爲止,若是尚未,則操做的屬性或者方法不存在,返回undefined

function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype.getX = function () {
    console.log(this.x);
};
Fn.prototype.getY = function () {
    console.log(this.y);
};
var f1 = new Fn;
var f2 = new Fn;
console.log(f1.getX === f2.getX);//=>false
console.log(f1.getY === f2.getY);//=>true
console.log(f1.__proto__.getY === Fn.prototype.getY);//=>true
console.log(f1.__proto__.getX === f2.getX);//=>false
console.log(f1.getX === Fn.prototype.getX);//=>false
console.log(f1.constructor);//=>Fn
console.log(Fn.prototype.__proto__.constructor);//=>Object
f1.getX();//=>this:f1 =>console.log(f1.x); =>100
f1.__proto__.getX();//=>this:f1.__proto__ =>console.log(f1.__proto__.x); =>undefined
f2.getY();//=>this:f2 =>console.log(f2.y); =>200
Fn.prototype.getY();//=>this:Fn.prototype =>console.log(Fn.prototype.y); =>undefined
複製代碼

私有屬性和共有屬性

私有屬性

本身堆內存中存儲的屬性相對本身來講是私有的

公有屬性

本身基於__proto__找到的屬性,相對本身來講是共有的

function fun(){
	this.a=0;
	this.b=function(){
		alert(this.a);
	}
}
fun.prototype={
	b:function (){
		this.a=20;
		alert(this.a);
	},
	c:function (){
		this.a=30;
		alert(this.a)
	}
};
var my_fun=new fun();
my_fun.b();
my_fun.c();
複製代碼

構造原型設計模式

在實際項目基於面向對象開發的時候(構造原型設計模式),咱們根據須要,不少時候會重定向類的原型(讓類的原型指向本身開闢的堆內存)

存在的問題

一、本身開闢的堆內存中沒有constructor屬性,致使類的原型構造函數缺失(解決:本身手動在堆內存中增長constructor屬性)

二、當原型重定向後,瀏覽器默認開闢的那個原型堆內存會被釋放掉(在沒有被佔用的狀況下),若是以前已經存儲了一些方法或者屬性,這些東西都會丟失,因此內置類的原型不容許重定向到本身開闢的堆內存,由於內置類原型上自帶不少屬性方法,重定向後都沒了,這樣是不被容許的,瀏覽器默認不讓修改,就算修改了也沒用。

function Fn(){
	console.log("ok");
}
var f1=new Fn();
Fn.prototype={
	name:"zhufeng",
	age:18
}
var f2=new Fn();
複製代碼

當咱們須要給自定義類的原型批量設置屬性和方法的時候,通常都是讓原型重定向到本身建立的對象中

function Fn={

}

Fn.prototype={
	constructor:Fn,
	aa:function(){
	}
}
複製代碼

練習題

function Fn() {
    var n = 10;
    this.m = 20;
    this.aa = function () {console.log(this.m);}
}
Fn.prototype.bb = function () {console.log(this.n);};
var f1=new Fn;
Fn.prototype={
    aa:function(){console.log(this.m+10);}
};
var f2=new Fn;
console.log(f1.constructor);//=>f Fn
console.log(f2.constructor);//=>f Object
f1.bb();//=>undefined
f1.aa();//=>20
f2.bb();//=>報錯
f2.aa();//=>20
f2.__proto__.aa();//=>NaN
複製代碼

####內置類的原型擴展方法

設置內置方法,能夠供它的實例調取使用

1.手動增長的方法最好設置"my"前綴(本身定),防止把內置方法重寫

//=>方法中的this通常都是當前類的實例(也就是咱們要操做的數組)
//=>操做this至關於操做ary,方法執行完成會改變原有數組
Array.prototype.myUnique=function myUnique(){
	var obj={};
	for(var i=0;i<this.length;i++){
		var item=this[i];
		if(item in obj){
			this[i]=this[this.length-1];
			this.length--;
			i--;
			continues;
		}
		obj[item]=item;
	}
	obj=null;
};
ary.myUnique();//=>this:ary
複製代碼

練習:給Number類增長自定義方法plus和minus

plus:加一個數 minus:減一個數

Number.prototype.plus=function plus(n){
	this=this+Number(n)//=>一、this是不能被賦值 二、此時左邊this至關於變量,變量也不能使用關鍵字
	return this+Number(n);
}
Number.prototype.minus=function minus(m){
	return this-Number(m)
}
複製代碼
JS中的鏈式寫法

保證每個方法執行返回的結果依然是當前類的實例,這樣就能夠繼續調取方法使用了

ary.sort(function (a,b){return a-b};).reverse().pop();

//=>pop方法的返回值是刪除的那一項

---------------------------------

ary.sort(function (a,b){return a-b;}).reverse().slice(2,7).join("+").split("+").toString().substr(2).toUpperCase();
複製代碼

函數的三種角色

一、普通函數

  • 堆棧內存釋放
  • 做用域鏈

二、

  • prototype:原型
  • _proto_:原型鏈
  • 實例

三、普通對象

function Fn(){
	var n=10;
	this.m=100;
}

Fn.prototype.aa=function (){
	console.log("aa");
}

Fn.bb=function (){
	console.log("bb");
}
var f=new Fn();
複製代碼

JQ這個類庫中提供了不少方法,其中有一部分寫在原型上,有一部分是把它當作普通對象來設置的

~function(){
	function jQuery(){
		
		return [JQ實例]
	}
	jQuery.prototype.animate=function(){};
	jQuery.ajax=function(){};
	window.jQuery=window.$=jQuery;
}();

$()//=>是JQ的一個實例,只能調取jQuery原型上的屬性
$().ajax()//=>調取不了,那是jQuery類自己的屬性
$().animate()//=>能夠調取
$.ajax()//=>直接的對象鍵值對操做
$.animate()//=>對象上沒有animate這個屬性,這個屬性和實例相關的原型上
複製代碼

函數三種角色的運行機制圖

function Fn() {
    this.n = 100;
}
Fn.prototype.getN = function () {
    console.log(this.n);
};
Fn.AA = 200;
var f=new Fn();
複製代碼

阿里面試題

function Foo() {
    getName = function () {
        console.log(1);
    };
    return this;
}
Foo.getName = function BBB() {
    console.log(2);
};
Foo.prototype.getName = function AAA() {
    console.log(3);
};
var getName = function () {
    console.log(4);
};
function getName() {
    console.log(5);
}
Foo.getName();//=>2 把Foo當作一個對象,找Foo的私有方法執行
getName();//=>4 執行全局下的GET-NAME
Foo().getName();//=>1 先把FOO當作普通函數執行,執行返回的結果(this爲window)在調取GET-NAME執行
getName();//=>1 執行的依然是全局下的GET-NAME
new Foo.getName();//=>2 成員訪問的優先級高於無參數列表new =>Foo.getName([F->2])=>new [F->2]()
new Foo().getName();//=>3 成員訪問和有參數列表new優先級都是19,按照從左到右依次執行便可 =>創造Foo的一個實例f =>f.getName()=>3
new new Foo().getName();//=>3 先算new Foo(),創造一個實例f =>new f.getName() =>繼續執行優先級高的成員訪問 =>f.getName =>[F->3] =>new [F->3]()=>構造函數執行這個方法 
複製代碼

Function類的call/apply/bind方法

call/apply/bind用來改變某一個函數中的this關鍵字指向的 只要是函數均可以調取這三個方法 ####call方法 語法:[fn].call([this],[param].....) fn.call():當前實例(函數Fn)經過原型鏈的查找機制,找到Function.prototype上的call方法,把call方法執行

call方法執行的步驟

一、把call方法中的this的"this關鍵字"改成arguments[0] 二、把arguments中除了第一項之外的實參傳遞給this,執行this

fn.call();
Function.prototype.call=function (){
	let param1=arguments[0],
		paramOther=[];//=>把arguments中除了第一個之外的實參獲取到
	
	//=>call方法中的this:fn 當前要操做的函數(函數類的一個實例)
	//把Fn中的關鍵字修改成param1=>把this(call中)的this關鍵字修改成param1
     this.toString().replace("this",param1);
     this(paramOther);
	//=>把fn執行,把paramOther分別傳遞給fn
}
複製代碼

細節

1.非嚴格模式下,若是參數不傳,或者第一個傳遞的是null/undefined,this都指向window 2.在嚴格模式下,第一個參數是誰,this就指向誰(包括null/undefined),不傳this是undefined

let fn=function (a,b){}

fn.call(obj,10,20);//=>this:obj a=10 b=20
fn.call(10,20);//=>this:10 a=20 b=undefined
fn.call();//=>this:window a=undefined b=undefined
fn.call(null);//=>this:window
fn.call(undefined);//=>this:window
複製代碼
function fn1(){console.log(1);console.log(this);}
function fn2(){console.log(2);console.log(this);}

fn1.call(fn2);
fn1.call.call(fn2);

//fn1.call至關於Function類原型上的call方法
//call方法也是一個函數,因此也能夠調取Function類原型上的call方法,那麼至關於call方法(先命名爲A)調取了call方法並執行
//call方法執行的第一步先把this中的「this」關鍵字改成fn2,也就是把A(call方法中的this改成fn2)
//call方法執行的第二步把this執行,也就是A(call方法)執行
//A執行也就是call方法又執行一遍,無實參,此時call方法中的this已經變爲fn2了,那麼第一步把this中的「this關鍵字」改成實參第一項(undefined),第二步把this執行也就是把fn2執行,無傳參,輸出2


非嚴格模式下執行,控制檯會輸出thiswindow,由於沒有傳參數,因此this指向window,切換到嚴格模式下控制檯就會輸出undefinedFunction.prototype.call(fn1)
//=>線找到call把它執行,call中的this是Function.prototype=>讓F.P中的this變爲FN1,而後讓F.P執行,F.P是一個匿名函數也是一個空函數,執行沒有返回值,也就是沒有任何輸出

Function.prototype.call.call(fn1);
//=>先找到CALL-AA把它執行,它中的THIS是F.P.CALL =>把F.P.CALL中的THIS修改成FN1,讓F.P.CALL執行 =>F.P.CALL(CALL-AA)第二次把它執行(此時它裏面的THIS已是FN1) =>這一次其實在CALL-AA中是讓FN1執行 =>1
複製代碼

規律

1.一個call,執行的是call前面的函數 2.兩個及兩個以上call,執行的是括號中傳的第一個實參

apply方法

語法和call基本如出一轍,惟一的區別在於傳參方式,apply是把這些值放到一個數組或者類數組中,可是也至關於一個個的傳遞給函數(語法不同可是做用如出一轍)

fn.call(obj,10,20)
fn.apply(obj,[10,20])
//=>apply把須要傳遞給fn的參數放到一個數組(或者類數組)中傳遞進去,雖然寫的是一個數組,可是也至關於給fn一個個的傳遞
複製代碼

案例:獲取數組中的最大值 第一種

給數組先排序(由大到小排序),第一項就是最大值

let ary=[12,23,4,5,2,67,89];
let max=ary.sort(function (a,b){return b-a;})[0];
console.log(max);
複製代碼

第二種

假設法:假設第一個值時最大值,依次遍歷數組中後面的每一項,和假設的值進行比較,若是比假設的值要大,把當前項賦值給MAX

let ary=[12,3,4,2,32,34,12,45,23,4,5,34];
let max=ary[0];
for(let i=1;i<ary.length;i++){
	let item=ary[i];
	item>max?max=item:null;
}
console.log(max);
複製代碼

第三種

基於Math.max和eval方法實現

let ary=[12,3,4,2,32,34,12,45,23,4,5,34];
ary=ary.toString()
let max=eval("Math.max("+ary+")");
console.log(max);

//=>Math.max("12,23,1,24"),此時會把"12,23,1,24"當作一項,而後調用Number()方法,轉換爲數字,因此會輸出NaN
//=>Math.max(12,23,45,"89")=>89
複製代碼

知識點擴充:括號表達式

用小括號包起來,裏面有不少項(每一項用逗號分隔),最後只獲取最後一項的內容(可是會把其餘的項也都過一遍)

(function(){console.log(1);},function(){console.log(2);})();//=>2

-----------------------
let a=1===1?(12,23,14):null;//=>14
複製代碼

不建議你們過多使用括號表達式,由於會改變this

let fn=function(){console.log(this);}
let obj={fn:fn};
(fn,obj.fn)();//=>執行的是第二個obj.fn,可是方法中的this是window而不是obj
(obj.fn)();//=>this:obj
複製代碼

第四種

基於Math.max和apply方法實現

let ary=[12,3,4,2,32,34,12,45,23,4,5,34];
let max=Math.max.apply(null,ary);
console.log(max);

//=>Math.max是一個函數,因此能夠調取Function.prototype上的apply方法
//=>apply方法:
//1.把this中的「this關鍵字」改成arguments[0]
//2.把this執行,並把arguments[1]...這些實參傳遞給this(以數組的形式傳參),雖然是數組的形式傳參,可是也是一個一個的傳遞
複製代碼

第五種

基於ES6中的展開運算符

let max=Math.max(...ary);
console.log(max);
複製代碼

bind方法

語法和call如出一轍,惟一的區別在於當即執行仍是等待執行

fn.call(obj,10,20)
//=>改變fn中的this,而且把fn當即執行

fn.bind(obj,10,20)
//=>改變fn中的this,此時的fn並無執行
複製代碼

案例

需求:點擊的時候執行fn,讓fn的this是obj

let fn=function (a,b){
	console.log(this);
};
let obj={name:"OBJ"};

document.onclick=fn;//=>this:document

document.onclick=fn.call(obj);//=>雖然this確實改成obj了,但過後綁定的時候就把fn執行了(call是當即執行函數),點擊的時候執行的是fn的返回值undefined

document.onclick=fn.bind(obj);//=>bind屬於把fn中的this預處理爲obj,此時fn沒有執行,當點擊的時候纔會把fn執行
複製代碼

案例:定時器

//=>設置一個定時器,把一個方法賦值給定時器,設定等待的時間,當到達時間後,把方法執行(1000MS)
setTimeout(function(){console.log(1);},1000);

------------------
setTimeout(fn.call(obj,10,20),1000);
//=>call是當即執行,在設置定時器的時候,就把fn執行了(此時也基於call改變了this),把fn執行的返回值賦值給定時器,1000ms後執行的是fn的返回值

--------------------
setTimeout(fn.bind(obj,10,20),1000);
複製代碼

解構賦值

按照一個數據值的結構,快速解析獲取到其中的內容,真實項目中通常都是針對於數組或者對象進行解構賦值

數組

讓等號左邊出現和右邊相同的數據結構,左邊能夠建立一些變量快速獲取到右側對應位置的值

let ary=[1,2,3];
let [a,b,c]=ary;
console.log(a,b,c);
複製代碼
//=>a和b互換位置
let a=12,
	b=13;
[a,b]=[b,a];
console.log(a,b);
複製代碼

得到數組中的某一項

let ary=[1,2,3,4,5];
let [,b,,c,d=0]=ary;
console.log(b,c,d)//=>2,4,0
//=>d默認賦值爲0,若是當前項沒有解構到任何值,給一個初始值;若是d不賦值默認值的話獲得的應該是undefined
-------------
let ary=[1,2,3,4,5];
let [a]=ary;
console.log(a);//=>1
複製代碼

拓展運算符

"..."稱之爲拓展運算符:除了前面之外的項,都放在一個數組中 剩餘運算符只能處於結構中最後的位置

//=>需求:獲取第一項,把剩下的項做爲一個數組返回
let ary=[12,23,44,54,34];
let [a,...b]=ary;
console.log(a,b);//=>12 [23,44,54,34]

--------------------
let ary=[12,23,44,54,34];
let [a,...b,c]=ary//=>Uncaught SyntaxError: Rest element must be last element
複製代碼
function fn(context,...arg){
	console.log(context,arg);
	//=>arg是個數組,arguments是類數組,這樣arg就能夠用數組的方法
}
fn(obj,10,20,30);
複製代碼

展開運算符

把數組(對象/類數組)中的每一項展開

let ary=[1,23,12,45]
let max=Math.max(...ary);
console.log(max)//=>45

----------------
let ary=[1,2,3,4,5];
let [...newAry]=ary;//=>數組克隆
let newAry=ary.slice();

-----------------
let ary=[1,2,3,4,5];
let b=[...ary,1,2,3];
console.log(b);//=>[1,2,3,4,5,1,2,3]
複製代碼

對象

對象解構賦值默認狀況下要求:左邊變量名和對象中的屬性名一致才能夠

let obj={name:"xxx",age:12,sex:0};
let {name,age}=obj;
console.log(name,age);//=>"xxx",12

let {sex}=obj;
console.log(sex);//=>0
複製代碼
let data={
	name:"xxx",
	age:27,
	score:[110,130,20]
};
let {name,score:[chinese,,english=0]}=data;
console.log(name,chinese,english);//=>("xxx",110,20)
複製代碼

起別名

給解構的屬性名起別名做爲咱們使用的變量

let obj={name:"xxx",age:12,sex:0};
let {age:ageAA}=obj;
console.log(ageAA);//=>12
console.log(age);//=>Uncaught ReferenceError: age is not defined
複製代碼

給不存在的屬性設置默認值

let obj={name:"xxx",age:12,sex:0};
let {friend=0}=obj;
console.log(friend);//=>0

--------------
let obj={name:"xxx",age:12,sex:0};
let {hobby:myHobby="lanqiu"}=obj;
let {name:myName="aa"}=obj;
console.log(myName, myHobby);//"xxx",12
複製代碼

剩餘運算符

剩餘運算符只能處於結構中最後的位置

let obj={name:"zhuefn",age:19,sex:0,hobby:"bas"};
let {name:a,age:b,...c}=obj;
console.log(a, b, c);
//=>"zhuefn",19,{sex:0,hobby:"bas"}
----------------
let obj={name:"zhuefn",age:19,sex:0,hobby:"bas"};
let {name:a,age:b,...c,d}=obj;
console.log(a, b, c,d);//=>Uncaught SyntaxError: Rest element must be last element
複製代碼
//=>把傳遞的對象解構了(不傳遞值,默認賦值爲空對象),解構的時候若是某個屬性不存在,咱們賦值默認值
let fn=function ( { name:myName="zhufeng", age:myAge=0 }={} ){
    console.log(myName, myAge);
    console.log(name,age);//=>name至關於window.name(name是window這個實例自帶的屬性,屬性值爲空)
    //=>報錯:age is not defined

};
fn({name:"xxx",age:18});
複製代碼

展開運算符

let obj={name:"xxx",age:20};
let newObj={...obj,sex:0};//=>{name:"xxx",age:20,sex:0} 把原有的對象克隆一份
複製代碼

案例:任意數求平均數

需求:編寫一個方法fn,實現任意數求平均數(去除數字中的最大和最小,而後在算平均數,保留小數點後面兩位)

let fn=function(){
	let ary=[];
	for(let i=0;i<arguments.length;i++){
		let item=arguments[i];
		ary.push(item);
	}
	ary.sort(function(a,b){return a-b});
	ary.pop();
	ary.shift();
	let average=eval(ary.join("+")/ary.length);
	return average.toFixed(2);
}
複製代碼

重寫slice方法

mySlice方法,克隆數組

Array.prototype.mySlice=function mySlice(){
	let curary=[];
	for(let i=0;i<this.length;i++){
		let item=this[i];
		curary.push(item);
	}
	return curary
}
複製代碼

arguments借用數組方法

前提類數組和數組相似,都有length和索引

let fu=function (){
	let ary=[].slice.call(arguments,0);
		ary.sort(function(a,b){return a-b});
	ary.pop();
	ary.shift();
	let average=eval(ary.join("+")/ary.length);
	return average.toFixed(2);
}
複製代碼
let fn=function (){
	let arg=arguments;
	[].sort.call(arg,function(a,b){return a-b;});
	[].pop.call(arg);
	[].shift.call(arg);
	return eval([].join.call(arg,"+"))/arg.length;
}
複製代碼
let fn=function (){
	let arg=arguments;
	arg.__proto__=Array.prototype;
	//=>在IE瀏覽器中屏蔽開發者使用__proto__
	
	arg.sort(function (a,b){return a-b});
	arg.pop();
	arg.shift();
	return eval(arg.join("+"))/arg.length;
}
複製代碼

使用展開運算符

let fn=function (...ary){
	ary.sort(function (a, b) {
        return a - b;
    }).pop();
    ary.shift();
    return (eval(ary.join('+')) / ary.length).toFixed(2);
}
複製代碼
let fn=function(){
	let ary=[...arguments];
	ary.sort(function (a, b) {
        return a - b;
    }).pop();
    ary.shift();
    return (eval(ary.join('+')) / ary.length).toFixed(2);
}
複製代碼

芳香社交電商小程序邀請碼:VVTWXA,閒暇時間較多的能夠了解一下,很容易就能夠賺個外快,須要幫助私信加微信。

相關文章
相關標籤/搜索