1. 程式人生 > >前端學習(整理)物件,原型,上下文環境,作用域,閉包等問題深入理解

前端學習(整理)物件,原型,上下文環境,作用域,閉包等問題深入理解

物件

物件是屬性的集合,重點:是一個集合,意思是有一個個屬性整合在了一起,組成了一個集合,這個集合叫做物件,另外,這個屬性是以鍵值對的形式存在的,鍵是一個字串,比如a,比如name等,值可以是任意型別,比如陣列,function,字串,數字

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

prototype(原型)

每一個函式建立的時候,系統會自動給這個函式附加一個叫prototype的屬性,這個prototype,我們稱作為原型,而這個叫做prototype,也就是原型的屬性本身是一個物件,它的本身只有一個屬性就是constructor,意思是將這個原型指向這個函式,從而不會弄錯父級.

雖然這個prototype只有一個自帶的屬性,但是我們可以給它擴充套件,例如:

function Aa(){}

Aa.prototype={a:1,b:2}

另外,這邊還有一個__proto__,其指向的是該原型物件的prototype,這個屬性是一個私有屬性,是各個大瀏覽器廠商新增的一個屬性,但是因為其廣泛的使用,被大家所流傳,其作用和prototype一樣,也是一個原型,如果硬要說區別,大概就是顯性原型(prototype)和隱性原型(__proto__)把,看個例子

function Aa(){}

Aa.prototype.__proto__ === Object.prototype //true

function AA(){}

let a=new AA(){}

a.__proto__ === AA.prototype //true

解釋:

a是AA的例項物件,因此,它的__proto__指向的是其原型物件的prototype,也就是AA.prototype

Aa.prototype不僅是a的原型物件,同時自己也是一個例項物件,所以它的__proto__是Obejct.prototype

 

注意1:頂級的Obejct.prototype,它的__proto__是null,因為它已經是頂級了,沒有原型物件

 

原型鏈

原型鏈,原型鏈,說白了就是一條鏈,也就是一層一層的繼承下來的一條鏈式,例如:

function FOO(){}

FOO.prototype={}

let foo=new FOO();

 

這裡的foo,是繼承的FOO上的屬性方法,而FOO除了自身prototype上的方法屬性,還有從Object上繼承來的方法和屬性,從foo->FOO->Object,這就是一條原型鏈,foo不僅從FOO處繼承到了屬性方法,還從Object處繼承到了屬性和方法

注意1:如何判斷是否是自己自由的屬性或方法,而不是從原型鏈上繼承到的,這裡有一個方法可以判斷,hasOwnProperty(),如下例

function aa(params) {}

let a=new aa();

a.name=1;

aa.prototype.name=1;

aa.prototype.age=2;

console.log(aa.prototype); //{name:1,age:2}

console.log(a); //{name:1}

for (item in a){

if(a.hasOwnProperty(item)){

console.log(item); //{name:1}

}

}

上下文環境

1.var,let,const

宣告一個變數通常有三種方法,var以及在ES6中引入的let,const

var:是一種全域性宣告,即使宣告在呼叫之後,也只會是顯示undefined,而不會報錯

console.log(a) //undefined

var a=10;

let和const:呼叫必須在宣告之後,如果在之前,就會報錯

console.log(a,b) //丟擲異常錯誤

let a=10;

const b=10;

2.this

this,這個值在任何地方都有值,而且情況很複雜

第一種情況,當做建構函式中被呼叫了

function FOO(){

this.name='aa';

this.age='bb';

console.log(this) //FOO{name:'aa',age:'bb'}

}

let a=new FOO();

sonsole.log(a.name,a,age) //aa,bb

如果函式被當做建構函式使用,那麼這時的this代表的是new出來的物件

但是

如果函式是被直接呼叫了,而不是當做建構函式使用,沒有例項化,如下

function FOO(){

this.name='aa';

this.age='bb';

console.log(this) //window{...}

}

FOO()

那麼,此時的的this指向的是window

 

第二種情況,函式作為物件的一個屬性

如果函式作為物件的一個屬性,並且被作為屬性呼叫時,那麼此時的this指向的是當前的這個obj

let a={

x:1,

y:function(){

console.log(this) //a{x:1,y:function()}

console.log(this.x) //1

}

}

a.y();

如果函式作為一個物件的屬性,但是不是作為一個屬性被呼叫的,那麼此時的this指向的是window

let a={

x:1,

y:function(){

console.log(this) //window{}

console.log(this.x) //undefined

}

}

let ax=a.y;

ax();

 

第三種情況,函式使用call或者apply呼叫

此時的this指向的是當前傳入的這個物件

let obj={x:10}

let a=function(){

console.log(this) //obj{x:10}

console.log(this.x) //10

}

a.call(obj)

 

第四中情況

全域性和呼叫普通函式

全域性情況下的this === window,永遠等於

被當做普通函式呼叫時,this也永遠指向window,例如

let a={

x:10,

y:function(){

function ax(){

console.log(this); //window{}

console.log(this.x); //undefined

}

ax();

}

}

 

3.函式,函式宣告

函式也是一個全域性屬性即使呼叫在宣告之後,依舊可以被使用

aa() //10

function aa(){

console.log(this) //window

return 10;

}

 

函式宣告和函式不同,函式宣告不可以在宣告前呼叫,否則會丟擲異常錯誤

a(); //丟擲異常錯誤

let a=function(){

return 10;

}

 

另外,每次呼叫函式時,會產生一個新的上下文環境,並將老的上下文環境壓棧(也就是降低優先順序),假如這時候新的函式中有新定義的變數,且跟老的變數重名,那麼僅在該函式的作用域下新的變數會覆蓋老的變數直至脫離該函式的作用域,到函式呼叫結束,那麼這個被建立的新的上下文環境會被銷燬,而這個環境中的變數也會被銷

 

自由變數

例如:

var x=10;

function(){

var b=20;

console.log(b+x) //30;

}

這裡的x就是自由變數

 

另外:

自由變數x的取值,有人說是去父級作用域取,其實並不完全正確,正確的說法應該是,到建立的那個作用域去取,而不是呼叫的那個作用域,例如

var x=10;

function fn(){

console.log(x);

}

function show(f){

var x=20;

(function(){

fn//輸出為10,而不是20

})()

}

show(fn)

 

閉包

閉包這個兩個字的文字解釋,還真不好說,簡單的說就是函式去呼叫了其他作用域的變數,實際上閉包的應用,絕大多數只有兩種:函式作為返回值函式作為引數

1.函式作為返回值

function fn(){

var max=10;

return function bar(x){

if(x>max){

console.log(x)

}

}

}

var f1=fn();

f1(15); //15

2.函式作為引數

var max=10;

fn=function (x){

if(x>max){

console.log(x)

}

}

(function(){

var max=100;

fn(15)

})(fn)

這邊,max的取值為10,而不是15,原因是上面提到的一個函式的作用域取值要去建立的那個作用域取值,而不是呼叫的那個作用域。

正常情況下,當一個函式呼叫結束以後,這個函式的上下文環境會被銷燬,包括其中的變數,但是,閉包的核心就是不希望這個函式被銷燬

 

未完待續..