一.數(shù)據(jù)類型的操作原理
-
數(shù)據(jù)類型
-
基本數(shù)據(jù)類型(值類型)
- number
- string
- boolean
- null
- undefined
-
引用數(shù)據(jù)類型
-
對象
- {} 普通對象
- /^$/正則
- 一般類的實(shí)例都是對象數(shù)據(jù)類型的
- 函數(shù)的prototype屬性
- 實(shí)例的proto屬性
-
函數(shù)
- function 普通函數(shù)
- 類
- 。。。
console.log(typeof Object);//function
-
-
值類型
直接按值操作,例如:var a=12;直接把12這個賦值給變量a(讓a變量和12這個值建立了鏈接關(guān)系)
-
對象數(shù)據(jù)類型
在js中遇到對象,會嚴(yán)格按照如下的步驟操作
1.瀏覽器為其開辟一個新的內(nèi)存空間,為了方便后期可以找到這個空間,瀏覽器給空間分配一個16進(jìn) 制的地址
2.按照一定順序,分別的把對象鍵值對存儲到內(nèi)存空間中
3.把開辟內(nèi)存的地址賦值給變量(或者其他的東西),以后變量就可以通過地址找到內(nèi)存空間,然后進(jìn)行一些操作
- 分析例子
js代碼運(yùn)行在瀏覽器中,是因?yàn)闉g覽器給我們提供了一個供js代碼執(zhí)行的環(huán)境->全局作用域(window/global)
var a=12; var b=a; b=13; console.log(a); var o={name:"珠峰培訓(xùn)"}; var p=o; p.name="周嘯天" console.log(o.name); var m={name:"珠峰培訓(xùn)"}; var n=m; n={name:"中國最權(quán)威的前端培訓(xùn)機(jī)構(gòu)"}; console.log(m.name);image.png -
-
函數(shù)的操作
-
創(chuàng)建函數(shù)
1.先開辟一個新的內(nèi)存空間(為其分配了一個16進(jìn)制的地址)
2.把函數(shù)體中編寫的js代碼當(dāng)做字符串存儲到空間中(函數(shù)只創(chuàng)建不執(zhí)行沒有意義)
3.把分配的地址賦值給聲明的函數(shù)名(function fn和var fn操作原理其實(shí)相同,都是在當(dāng)前作用域聲明了一個名字)
-
執(zhí)行函數(shù)
目的:執(zhí)行函數(shù)體中的代碼
1.函數(shù)執(zhí)行的時候,瀏覽器會形成一個新的私有作用域(只能執(zhí)行函數(shù)體 中的代碼)供函數(shù)體中的代 碼執(zhí)行
2.執(zhí)行代碼之前,先把創(chuàng)建函數(shù)存儲的那些字符串變?yōu)檎嬲膉s表達(dá)式, 按照從上到下的順序在私有作用域中執(zhí)行,一函數(shù)可以被執(zhí)行N次,
每一次的執(zhí)行相互之間互不干擾形成的私有作用域把函數(shù)體中的私有變量都包裹起來了(保護(hù)起來了),在私有作用域中操作私有 變量和外界沒關(guān)系,外界也無法直接的操作私有變量
3.函數(shù)執(zhí)行具體步驟
- 形參賦值
- 變量提升
- 代碼自上而下執(zhí)行
- 當(dāng)前棧內(nèi)存(私有作用域)銷毀 (也有例外:不銷毀的時候,往下看閉包)
案例分析
var a=12; var b=a; b=13; console.log(a); var o={name:"珠峰培訓(xùn)"}; var p=o; p.name="周嘯天" console.log(o.name); var m={name:"珠峰培訓(xùn)"}; var n=m; n={name:"中國最權(quán)威的前端培訓(xùn)機(jī)構(gòu)"}; console.log(m.name); function fn(){ var ary=Array.prototype.slice.call(arguments); return eval(ary.join('+')); } fn(12); fn(13);image.png
-
二.堆棧內(nèi)存
-
棧內(nèi)存
俗稱叫做作用域(全局作用域/私有作用域)
- 為js代碼提供執(zhí)行環(huán)境(執(zhí)行js代碼的地方)
- 基本數(shù)據(jù)類型值是直接存放在棧內(nèi)存中的
-
堆內(nèi)存
存儲引用數(shù)據(jù)類型值的(相當(dāng)于一個存儲的倉庫)
- 對象存儲的是鍵值對
- 函數(shù)存儲的是代碼字符串
-
內(nèi)存釋放
在項(xiàng)目中,我們使用的內(nèi)存越少性能越好,我們需要把一些沒用的內(nèi)存處理掉
-
堆內(nèi)存
var o={};當(dāng)前對象對應(yīng)的堆內(nèi)存被變量o占用著呢,堆內(nèi)存是無法銷毀的
o=null;null空對象指針(不指向任何的堆內(nèi)存),此時上一次的堆內(nèi)存就沒有被占用了,谷歌瀏覽器會在空閑時間把沒有被占用的堆內(nèi)存自動釋放(銷毀/回收)
-
棧內(nèi)存
一般情況下,函數(shù)執(zhí)行形成的棧內(nèi)存,函數(shù)執(zhí)行完,瀏覽器會把形成的棧內(nèi)存自動釋放;
有的時候執(zhí)行完成,棧內(nèi)存不能被釋放?
全局作用域在加載頁面的時候執(zhí)行,在關(guān)掉頁面的時候銷毀
-
三.變量提升
-
定義及案例分析
- 定義
在當(dāng)前作用域中,js代碼自上而下執(zhí)行之前,瀏覽器首先會把所有帶
var/funciton關(guān)鍵字的進(jìn)行提前的聲明或者定義,也就是該變量不管是在當(dāng)前作用域的哪個地方聲明的,都會提升到所屬作用域的最頂上去
聲明(declare):var num ;在當(dāng)前作用域中吼一嗓子我有num這個名了
定義(defined): num=12;把聲明的名字賦一個值
帶var關(guān)鍵字的只是提前的聲明一下;
帶funciton關(guān)鍵字的在變量提升階段把聲明和定義都完成了
同一個變量只會聲明一次,其他的會被忽略掉
函數(shù)聲明的優(yōu)先級高于變量申明的優(yōu)先級
- 分析這一段代碼
console.log(num); console.log(fn); var num=13; function fn(){ console.log(a); var a=10; console.log(a); } fn(); console.log(num);image.png
-
不管條件是否成立都要進(jìn)行變量提升
不管條件是否成立,判斷體中出現(xiàn)的var/function,都會進(jìn)行變量提升;
但是在最新版瀏覽器中,function聲明的變量只能提前聲明不能定義了(前提:函數(shù)是在判斷體中)
console.log(num);//undefined console.log(fn);//undefined if(1==1){ console.log(num);//undefined console.log(fn);//fn函數(shù)本身 var num =12; function fn(){ } } console.log(fn);//fn函數(shù)本身代碼執(zhí)行到條件判斷的地方
-
條件不成立
進(jìn)入不到判斷體中,賦值的代碼執(zhí)行不了,此時之前的聲明的變量或者函數(shù)以然是undefined
-
條件成立
進(jìn)入到條件判斷體中的第一件事情不是代碼執(zhí)行,而是把之前的變量提升沒有定義的函數(shù)首先定義了(進(jìn)入到判斷體中的函數(shù)就有定義了)
老版本瀏覽器不是這樣處理的:不管條件是否成立,function都要進(jìn)行變量提升(聲明+定義)
新版瀏覽器function只是聲明
-
-
關(guān)于重名的處理
//重名的只會聲明一次,函數(shù)在提升階段就可以定義了就是賦值,可以多次賦值 //=>變量提升:fn=aaafff111 (=aaafff222) (=aaafff333) (=aaafff444) fn();//4 function fn(){console.log(1);} fn();//4 function fn(){console.log(2);} fn();//4 var fn=13; fn();//Uncaught TypeError: fn is not a function function fn(){console.log(3);} fn(); function fn(){console.log(4);} -
變量提升,函數(shù)優(yōu)先
//函數(shù)聲明和變量聲明都會被提升,但是需要注意的是函數(shù)會先被提升,然后才是變量 var fn1; //只定義沒有賦值,意味著原有棧中fn1的結(jié)果沒有改變 function fn1(){ } console.log(typeof fn1);//function 理由:var fn1;盡管出現(xiàn)在function fn1()之前,但它是重復(fù)的聲明,會被忽略,因?yàn)楹瘮?shù)聲明會被提升到普通變量之前 ( 其實(shí)也可以從另外一個角度去理解,提升變量聲明fn1, 但是函數(shù)聲明和定義都被提升上去的,最總fn1賦值是函數(shù)體 ) var fn1=4;//定義賦值,覆蓋函數(shù) function fn1(){ } console.log(typeof fn1);//number
四.作用域/作用域鏈
-
定義
-
作用域
【棧內(nèi)存】- 全局作用域:window
- 私有作用域:函數(shù)的執(zhí)行
- 塊級作用域:使用let創(chuàng)建變量存在塊級作用域
-
作用域鏈
當(dāng)前作用域代碼執(zhí)行的時候遇到一個變量,首先看一下它是否屬于私有變量,
如果不是私有變量,向其上級作用域查找,也不是上級作用域私有變量,繼續(xù)向上查找,
一直到window全局作用域?yàn)橹梗?/p>
我們把這種向上一級級查找機(jī)制叫做作用域鏈
-
-
查找私有變量
js中的私有變量有且只有兩種
在私有作用域變量提升階段,聲明過的變量(或者函數(shù))
形參也是私有變量
-
案例分析
- 案例1
image.png- 案例2
function fn(b){ //=>私有作用域 //=>形參賦值:b=1(私有變量) //=>變量提升:b=aaaff111 (此處賦值操作替換了形參賦值的內(nèi)容) console.log(b);//=>函數(shù) function b(){ //=>私有作用域 //=>形參賦值和變量提升都沒有 console.log(b);//=>函數(shù),不是私有變量,去上一級作用域查找, } b(); } fn(1); -
如何查找上級作用域
函數(shù)執(zhí)行形成的一個私有的作用域(A),A的上級作用域是誰,和它在哪執(zhí)行的沒有關(guān)系,主要看他在哪定義的,在哪個作用域下定義的,當(dāng)前A的上級作用域就是誰;
- 例1
var n=10; function sum(){ console.log(n);//10 } (function(){ var n=100; sum(); })();- 例2
var n=10; var obj={ n:20, fn:(function(){ var n=30; return function(){ console.log(n);//30 } })(), }; obj.fn();image.png- 例3
var n=10; var obj={ n:20, fn:(function(){ //上級作用域:全局作用域 return function(){ //上級作用域:自執(zhí)行函數(shù) console.log(n);//10 } })(), }; obj.fn();- 例4
var n=10; var obj={ n:20, fn:(function(n){ return function(){ console.log(n);//10 } })(obj.n) //Uncaught TypeError: Cannot read property 'n' of undefined //因?yàn)檫@時候obj還沒有呢 }; obj.fn();
五.閉包
-
閉包作用
-
保護(hù)作用
形成私有作用域,保護(hù)里面的私有變量不受外界的干擾
例如jqery代碼
(function(){ var jQuery=function(){ 。。。 } window.jQuery=window.$=jQuery; })(); jQuery(); -
保存作用
函數(shù)執(zhí)行形成一個私有作用域,函數(shù)執(zhí)行完成,形成的這個棧內(nèi)存一般情況下都會自動釋放
但是還有二般情況:函數(shù)執(zhí)行完成,當(dāng)前私有作用域(棧內(nèi)存)中的某一部分內(nèi)容被棧內(nèi)存以外的其他東西占用了,當(dāng)前的棧內(nèi)存就不能釋放掉,也就形成了不銷毀的私有作用域(里面得到私有變量也不會銷毀)
【形式】
? 函數(shù)執(zhí)行返回了一個引用數(shù)據(jù)類型堆內(nèi)存的地址,在外面有一個變量接收了這個返回值,此時 當(dāng)前作用域就不能銷毀,(想要銷毀,只需要讓外面的變量賦值為null,也就是不占用當(dāng)前 作用域的內(nèi)容了)
function fn(){ var i=1; return function(n){ console.log(n+ i++); } } var f=fn(); //只要f不銷毀,fn函數(shù)形成的私有作用域不會銷毀 f(10);//11 fn()(10);//11 //這種執(zhí)行完,fn函數(shù)形成的私有作用域就銷毀了 f(20);//22 fn()(20);//21image.png
-
六.原型
-
1.原理
所有的函數(shù)都天生自帶一個屬性:prototype(原型),它是一個對象數(shù)據(jù)類型的值,在當(dāng)前prototype對象中,存儲了類需要給其實(shí)例使用的公有的屬性和方法
prototype這個對象,瀏覽器會默認(rèn)為其開一個堆內(nèi)存,這個堆內(nèi)存中天生自帶一個屬性:constructor(構(gòu)造函數(shù)),這個屬性存儲的的值就是當(dāng)前函數(shù)本身
每一個類的實(shí)例(每一個對象)都天生自帶一個屬性:proto,屬性值是當(dāng)前對象所屬類的原型(prototype)
分析個例子
function Fn(name,age){ //這里面的屬性都是私有的 this.name=name; this.age=age; this.say=function(){ console.log(this.name); } } //原型上的都是公有的 Fn.prototype.say=function(){ console.log("hello world"); } Fn.prototype.eat=function(){ console.log("I like food"); } var f1=new Fn("王燕燕",19); var f2=new Fn("王雪超",69); f1.eat();image.png -
2.內(nèi)置類的原型分析
image.png/* 判斷是否是對象的對象屬性 hasOwnProperty 私有和公有是一個相對論,我們需要看相對于哪個對象而言: 1.相對于實(shí)例ary1來說push是公有的 2.相對于Array.prototype來說,push就是自己的私有的 3.凡是通過__proto__找到的屬性都是公有的,反之都是私有的 */ var ary1=[12,23]; console.log(ary1.hasOwnProperty("push"));//false console.log(Array.prototype.hasOwnProperty("push")); -
3重新構(gòu)造原型
function Fn(name){ this.name=name; this.say=function(){ console.log(this.name); } } Fn.prototype={ /*讓原型指向自己開辟的堆內(nèi)存有一個問題:自己開辟的堆內(nèi)存中沒有constructor這個屬性, 所以實(shí)例在調(diào)取constructor的時候找到的是Object,這樣不好, 此時我們應(yīng)該重新設(shè)置一下constructor,保證機(jī)制的完整性 */ constructor:Fn, aa:function(){}, bb:function(){} } var f =new Fn("wanglei"); console.log(f.constructor)//Fn函數(shù)
7.繼承
-
原型繼承
讓父類中的屬性和方法在子類實(shí)例的原型鏈上
-
特點(diǎn)
它是把父類的原型放到子類實(shí)例的原型鏈上,實(shí)例想調(diào)取這些方法,是基于
__proto__原型鏈查找機(jī)制完成的子類可以重寫父類上的方法(這樣會導(dǎo)致父類其它的實(shí)例也會收到影響)
父類中私有或者公有的屬性方法,最后都會變?yōu)樽宇愔泄械膶傩院头椒?/p>
代碼分析
function A(x){ this.x=x; } A.prototype.getX=function(){ console.log(this.x); } function B(y){ this.y=y; } //這2行代碼就實(shí)現(xiàn)了原型繼承 B.prototype =new A(200); B.prototype.constructor=B; B.prototype.getY=function(){ console.log(this.y); } var b=new B(100); console.log(b);image.png -
-
call繼承
-
特點(diǎn)
child方法中把parent當(dāng)做普通函數(shù)執(zhí)行,讓parent中的this指向child的實(shí)例,相當(dāng)于給child的實(shí)例設(shè)置了很多私有的屬性或者方法
只能繼承父類私有的屬性或者方法(因?yàn)槭前裵arent當(dāng)做普通函數(shù)執(zhí)行,和其原型上的屬性和方法沒有關(guān)系)
父類私有的變?yōu)樽宇愃接械?/p>
代碼
function A(x){ this.x=x; } A.prototype.getX=function(){ console.log(this.x); } function B(y){ //這行代碼實(shí)現(xiàn)了call繼承 A.call(this,200);//=>b.x=200 this.y=y; } B.prototype.getY=function(){ console.log(this.y); } var b=new B(100); console.log(b); -
-
寄生組合繼承:call繼承+類似原型繼承 (推薦)
特點(diǎn)
父類私有和公有的分別是子類實(shí)例的私有和公有屬性方法代碼分析
function A(){ this.x=10; } A.prototype.getX=function(){ console.log(this.x); } function B(){ //這行代碼繼承父類私有屬性 A.call(this); this.y=10; } //這2行代碼繼承父類原型上的屬性(公有屬性) B.prototype = Object.create(A.prototype); B.prototype.constructor=B; B.prototype.getY=function(){ console.log(this.y); } var b=new B(); console.log(b);image.png









