函數定義
函數的定義可以通過function關鍵字.也可以通過表達式
function show1(){}
let show2 = function(){}//匿名函數
show1()
show2()//通過變量調用
函數也可以通過內置的javascript函數構造器:Function()
定義.但是實際上不必使用函數構造器(構造函數)來創建函數
//構造器創建
let show = new Function("a","b","return a+b")
console.log(show(1,2))//3
//不使用構造器
let show = function(a,b){return a+b}
console.log(show(1,2))//3
小提示:在javascript中很多時候要避免使用new關鍵字
函數支持函數提升,即函數調用在函數定義之前.注意函數表達式不支持函數提升
函數是對象
雖然使用 typeof
判斷函數類型返回的是 “function”.但是將函數認爲是對象更加合適
函數有屬性和方法.比如toString()
方法
函數裏面還內置了一個arguments
對象,通過該對象可以獲取有關參數的值.
創建對象
創建對象方式有兩種,第一種:直接創建實例對象.第二種通過構造函數創建對象
//直接創建對象實例
let person = {
name:'陳林',
age:18,
function show(){}//對象的方法
}
//通過構造函數創建對象
function Person(name,age){
this.name = name
this.age = age
}
let person = new Person("陳林",18)
額.顯然這裏的函數被稱爲構造函數.函數被用來創建對象時,稱爲構造函數
.函數寫在對象內部,便稱爲方法
.
函數裏面this
的指向問題
一般來說 this
指向函數執行時的當前對象,也就是誰來調用了這個函數
當函數沒有被自身對象調用時,this
就會指向全局對象window
有了構造函數的概念後,下面就講原型對象
原型對象
首先假如構造函數被定義好後,再往構造函數裏面添加方法.然後創建對象.,其創建的對象就不能訪問到添加的方法
function Show(name,age){//構造函數
this.name = name
}
Show.age =function(){console.log('年齡')}
let show = new Show("陳林")
console.log(show.age)//undefined
但是通過這樣,卻可以讓對象訪問到
function Show(name,age){//構造函數
this.name = name
}
Show.prototype.age =function(){console.log('年齡')}
let show = new Show("陳林")
console.log(show.age)//顯示方法
每一個函數(包括構造函數)都有prototype
屬性都會指向一個原型對象.而每一個實例對象都有一個__proto__
屬性同樣指向一個原型對象.它們兩個指向的原型對象就是同一個.
補充一下,原型對象中也有一個constructor
屬性來指向函數(構造函數).所以關係爲:構造函數通過prototype
指向原型對象,而原型對象通過constructor
指向構造函數.互相可以訪問.
備註: 原型對象可以看做一個空的object對象.這個的空可以理解爲我們沒有往裏面添加屬性或者方法.這裏的實例對象看作是根據構造函數而創建的.
基本流程爲: 構造函數Person()和實例對象person都指向同一個原型對象.但是原型對象也是對象,是一個空的object實例對象.所以堆空間一開始就有Object()構造函數對象.來創建這個空的object實例對象.這樣它們兩個就同時指向它們的原型對象(Object.prototype).但是最終這個Object構造函數的原型對象的__proto__
值爲null,原型鏈結束.
對應代碼:
function Person(name) {
this.name = name
}
let person = new Person("陳林")
Person.age = function () {
console.log('年齡')
}
person.age()// 年齡
顯然,通過Person.prototype.age=function(){}
來往原型對象中增加方法.其實例對象person
也就可以通過__proto__
屬性來訪問到了.因爲實例對象和構造函數都指向同一個原型對象.
原型鏈
當在一個對象中調用一個方法時,首先會查看這個方法是否在本對象中,如果沒有則會往該對象的原型對象裏面找(通過__proto__
),如果該對象的原型對象中也沒有,會再往原型對象的原型對象中查.直到找到Object的原型對象.如果Object的原型對象裏面也沒有則返回undefined(因爲Object的原型對象中__proto__
爲null
).
這樣就構成了一條__proto__
鏈.稱爲原型鏈.
注意,一般原型鏈中可以加入方法,也可以讀取方法.但是對於對象的屬性來說:讀取屬性時,會往原型鏈中查找,而添加屬性時,不會查找原型鏈(本對象有屬性則覆蓋,沒屬性則添加).
一般通過prototype
來添加方法,而不添加屬性,並且通常不會通過__proto__
來往原型鏈中添加
完整原型鏈
解析: 首先是創建f1
實例對象,所以實例對象與Foo()構造函數
同時指向Foo.prototype
.又因爲Foo.prototype
也是Object的實例對象
,所以與Object()構造函數
同時指向Object.prototype
.
然後是Foo()構造函數
也是Function的實例對象
,所以Foo()
與Function()構造函數
同時指向Function.prototype
.
最後是幾處特別的:
圖中創建o1
o2
是Object
的實例對象,所以通過__proto__
來指向Object.prototype
圖中Function()構造函數
既有prototype
指向Function.prototype
,又有__proto__
來指向Function.prototype
. 說明Function()構造函數
既是實例對象,又是構造函數.所以Function()構造函數
可以表示爲let Function = new Function()
圖中Object()構造函數
也是Function的實例對象
,所以通過__proto__
來指向Function.prototype
.
順便補充一下instanceof
判斷依據
表達式 A instanceof B
如果B函數的prototype(B的原型對象) 在A對象的原型鏈上,返回true 否則返回false
總結一下
一切對象都是Object
的實例,一切函數都是Function
的實例對象
構造函數通過 prototype
屬性訪問原型對象.
實例對象通過 __proto__
內部屬性訪問原型對象
js調用函數時,傳遞參數時,是值傳遞還是引用傳遞
理解1: 都是值傳遞,當傳遞的是基本類型時,傳遞的是值.當傳遞的是引用類型時,傳遞的也是值,只不過這個值是地址而已.
理解2: 基本類型是值傳遞,引用類型是引用傳遞.
對此需要特別強調:
let a = {name:'chenlin'}
let b = a
a= {name:'test'}
console.log(a.name)//test
console.log(b.name)//chenlin
//說明:當兩個變量指向同一個對象時,假如其中一個對象指向了另外一個對象時,兩個變量就沒有干係了!!!
//只有修改對象的值纔會使兩個值都會改變.
let a = {name:'chenlin'}
let b = a
a.name = 'test'
console.log(a.name)//test
console.log(b.name)//test
ES6有關函數的補充
解構參數
當參數時數組時,函數參數可將數組元素分散爲單個元素(參數用多個值來接收元素)
當參數是對象時,用來接收對象屬性的參數值必須和對象屬性名相同
const obj = {
x1: "內容1",
x2: "內容2",
x3: "內容3",
}
function fun({x1, x2, x3}) {
return `${x1} ${x2} ${x3}`
}
console.log(fun(obj))//內容1 內容2 內容3
展開運算符 …
可以使用展開運算符(…
)收集剩下參數(剩餘參數全部放在一個數組裏,而且必須放在最後)
const a = ["x1", "x2", "x3", "x4", "x5"];
function fun(x1, x2, ...arr) {
console.log(arr)
}
fun('chen','lin',a)//["x1", "x2", "x3", "x4", "x5"]
函數默認參數
function foo(a = 1,b = 2){
console.log(a,b)
}
foo(5)//5 2
箭頭函數this問題
箭頭函數中本身沒有this
,所以的this
都是上一作用域的this
call 方法與apply方法
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
}
function show(age) {
console.log("我的年齡" + age)
}
let person = new Person("陳林", 20)
show.call(person, person.age)//我的年齡20
使用方法: 本來person
對象中沒有一個方法,但是使用call
方法將show
函數臨時作爲person對象的方法使用一次.即等價於person.show(person.age)
但是不能這樣寫,因爲該對象中沒有show
方法
apply
方法與call
效果一樣,唯一區別就是,傳入的參數必須爲數組,而call是逐個傳參.
兩個函數的效果總結爲: 讓函數成爲任意指定對象的方法進行調用