溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript 精粹 基礎(chǔ) 進(jìn)階(6)函數(shù)和作用域(函數(shù)、this)

發(fā)布時(shí)間:2020-06-24 01:29:20 來源:網(wǎng)絡(luò) 閱讀:148 作者:huanghanzhilian 欄目:web開發(fā)

轉(zhuǎn)載請注明出處

原文連接 http://blog.huanghanlian.com/article/5b698ef6b8ea642ea9213f4e

函數(shù)是一塊JavaScript代碼,被定義一次,但可執(zhí)行調(diào)用多次,js中的函數(shù)也是對象,所以js函數(shù)可以像其他對象那樣操作和傳遞所以我們也常叫js中的函數(shù)為函數(shù)對象。

函數(shù)概述

函數(shù)的構(gòu)成主要有幾個(gè)部分函數(shù)名,參數(shù)列表,函數(shù)體

JavaScript 精粹 基礎(chǔ) 進(jìn)階(6)函數(shù)和作用域(函數(shù)、this)

function foo(x, y) {
    if (typeof x === 'number' && typeof y === 'number') {
        return x + y;
    } else {
        return 0;
    }
}
var cdr = foo(1,2);
console.log(cdr);//3

需要注意的是函數(shù)的返回值是依賴與return語句的,如果沒有return語句,默認(rèn)會(huì)在所有代碼執(zhí)行以后返回一個(gè)undefined。那么這是一般的函數(shù)調(diào)用。

function foo(x, y) {
    if (typeof x === 'number' && typeof y === 'number') {} else {

    }
}
var cdr = foo(1, 2);
console.log(cdr);//undefined

如果是作為構(gòu)造器,外部使用new去調(diào)用的話,如果沒有return語句,或者return是基本類型的話,那么會(huì)將this作為返回

function foo(x, y) {
    if (typeof x === 'number' && typeof y === 'number') {} else {
        return x+y;
    }
    this.x=x+y;
}
var cdr = new foo(1, 2);
console.log(cdr.x);//3

重點(diǎn)

this

函數(shù)在不同的調(diào)用方式下他的this指向是不一樣的,并且不同的調(diào)用方式下也會(huì)有一些細(xì)微的差別。

arguments

函數(shù)里面有一個(gè)特殊的對象,叫arguments他和參數(shù)是有一定的連帶關(guān)系的。

function foo(a,b) {
    return arguments;
}
var cdr=foo(1,2)
console.log(cdr);

JavaScript 精粹 基礎(chǔ) 進(jìn)階(6)函數(shù)和作用域(函數(shù)、this)

不同的調(diào)用方式

直接調(diào)用 對象方法 構(gòu)造器 call/apply/bind
foo() o.method() new Foo() func.call(o);
函數(shù)聲明與表達(dá)式

創(chuàng)建函數(shù)有不同的方式,常見的兩種就是函數(shù)聲明函數(shù)表達(dá)式

函數(shù)聲明

function foo(a, b) {
    a=+a;
    b=+b;
    if (isNaN(a)||isNaN(b)) {   //isNaN() 函數(shù)用于檢查其參數(shù)是否是非數(shù)字值。是數(shù)字返回true,非數(shù)字返回false
        return;
    }
    return a+b;
}
var cdr = foo(1, 2);
console.log(cdr);//3

函數(shù)表達(dá)式

把一個(gè)匿名函數(shù)賦值給一個(gè)變量,這種就是函數(shù)表達(dá)式

var add = function(a, b) {
    //do sth
}

需要注意的是函數(shù)聲明的函數(shù)可以在函數(shù)前調(diào)用

add();
function add() {
    console.log(1);//1
}

但是函數(shù)表達(dá)式會(huì)報(bào)錯(cuò)Uncaught TypeError: add is not a function

add();
var add = function() {
    alert(1)
}

函數(shù)表達(dá)式-立即執(zhí)行函數(shù)表達(dá)式

把一個(gè)匿名函數(shù)用一個(gè)括號()括起來,然后再去直接調(diào)用,這種函數(shù)定義的方式呢,也叫作函數(shù)表達(dá)式,并且是立即執(zhí)行函數(shù)表達(dá)式。

(function() {
    console.log(1); //1
})();

return 函數(shù)

我們也可以將函數(shù)對象,作為一個(gè)返回值,因?yàn)楹瘮?shù)也是對象。這種形式也是函數(shù)表達(dá)式。

return function(){
    //do sth;
};

命名式函數(shù)表達(dá)式

這種并不常見,同樣賦值給給一個(gè)函數(shù),但是這個(gè)函數(shù)不是匿名函數(shù),而是有一個(gè)名字的函數(shù)。這就是命名式函數(shù)表達(dá)式

var add = function add() {
    //do sth
};

除了函數(shù)聲明和函數(shù)表達(dá)式創(chuàng)建函數(shù)以外,還有一種不常見的一種創(chuàng)建函數(shù)對象的方式。就是函數(shù)構(gòu)造器Function構(gòu)造器

Function構(gòu)造器

函數(shù)構(gòu)造器就是大寫的Function,

var add = new Function ('a','b','console.log(a+b);');
add(1,2);//3

var add = Function ('a','b','console.log(a+b);');
add(1,2);//3

可以通過new或者直接調(diào)用的方式 運(yùn)行,他們倆基本沒什么區(qū)別,

前面兩個(gè)參數(shù)是函數(shù)對象里面的行參,最后一個(gè)參數(shù)表示函數(shù)體里面的代碼。

用參數(shù)是字符串會(huì)帶來安全上的一些隱患。

Function構(gòu)造器去創(chuàng)建的函數(shù)里面,創(chuàng)建的變量仍然是局部變量,也可以使用立即調(diào)用。

Function ('var lop="local";console.log(lop);')();//Function構(gòu)造器去創(chuàng)建的函數(shù)并且立即調(diào)用
console.log(lop);//lop是局部變量外部無法訪問會(huì)報(bào)錯(cuò)

在立即調(diào)用函數(shù)中有l(wèi)o變量,他的內(nèi)部有一個(gè)df函數(shù)聲明函數(shù)這個(gè)函數(shù)內(nèi)可以拿到lo變量

(function() {
    var lo = "lo";

    function df() {
        console.log(lo)
    }
    df();
})();
//輸出lo

但是在Function構(gòu)造器中l(wèi)o卻訪問不到。
在立即調(diào)用函數(shù)里,lo不可訪問,全局變量l可以訪問。所以說Function構(gòu)造器很少使用

var l="l";
(function(){
    var lo="lo";
    Function ('console.log(l);console.log(lo);')();
})()

//l
//Uncaught ReferenceError: lo is not defined(…)

比一比三種創(chuàng)建函數(shù)的方式

函數(shù)聲明 函數(shù)表達(dá)式 函數(shù)構(gòu)造器
前置
允許匿名
立即調(diào)用
在定義該函數(shù)的作用域通過函數(shù)名訪問
沒有函數(shù)名
  • 前置:只有函數(shù)聲明會(huì)被前置,就好像被拉倒了最前面,說以說可以在函數(shù)聲明的位置之前,去調(diào)用這樣一個(gè)函數(shù)。
    但是函數(shù)表達(dá)式和函數(shù)構(gòu)造器是代碼執(zhí)行階段,才會(huì)去創(chuàng)建對應(yīng)的函數(shù)對象,所以不會(huì)被前置。

  • 允許匿名:函數(shù)表達(dá)式和函數(shù)構(gòu)造器都是允許匿名的,事實(shí)上函數(shù)構(gòu)造器只能是匿名的是不可以有名字的,但是函數(shù)聲明他的名字是不可以省略的,省略在比較新的瀏覽器會(huì)報(bào)錯(cuò)。

  • 立即調(diào)用:函數(shù)聲明是不能被立即調(diào)用的,如果你嘗試把函數(shù)聲明后面寫一個(gè)括號,那么會(huì)報(bào)一個(gè)異常,因?yàn)楹瘮?shù)聲明被提前解析,并且前置放到最前了,所以會(huì)報(bào)錯(cuò)。函數(shù)表達(dá)式和函數(shù)構(gòu)造器因?yàn)椴粫?huì)前置,所以表達(dá)式計(jì)算結(jié)果是一個(gè)函數(shù)對象的時(shí)候我們可以加一對括號把他立即去調(diào)用。

  • 在定義該函數(shù)的作用域通過函數(shù)名訪問:比如說fuction fd(){}在function同級作用域下,可以通過fd()去調(diào)用這個(gè)函數(shù),但是函數(shù)表達(dá)式和函數(shù)構(gòu)造器就不可以這樣。

  • 沒有函數(shù)名:事實(shí)上函數(shù)構(gòu)造器只能是匿名的是不可以有名字的
[JavaScript]this

在不同的環(huán)境下,同一個(gè)函數(shù)在不同的調(diào)用方式下,這個(gè) this都有可能是不同的。

全局作用域下的this

全局作用域的 this一般指向全局對象,那么在瀏覽器里面這個(gè)全局對象就是window

console.log(this.document === document); //true
console.log(this === window); //true

可以看到this.document等價(jià)于window.document
this等價(jià)于window

this.a = 37;
console.log(window.a); //37

給全局對象添加一個(gè)屬性a,這樣就相當(dāng)于創(chuàng)建了一個(gè)全局變量a,并且賦值為37。

一般函數(shù)的this(瀏覽器)

function f1() {
    return this;
}
console.log(f1() === window); //true

var f1 = function() {
    return this;
}
console.log(f1() === window); //true

用函數(shù)聲明或者函數(shù)表達(dá)式,返回this,這里的this指向全局對象。

var f2 = function() {
    "use strict";
    return this;
}
console.log(f2()); //undefined

需要注意的是再嚴(yán)格模式下那么默認(rèn)一般情況下this會(huì)指向

作為對象方法的函數(shù)的this

var o = {
    prop: 37,
    f: function() {
        return this.prop;
    }
};
console.log(o.f()); //37

創(chuàng)建了一個(gè)對象字面量o,o里面有一個(gè)屬性f值呢是一個(gè)函數(shù)對象,對于一個(gè)把函數(shù)作為對象屬性值的方式,叫做對象的方法,作為對象的方法去調(diào)用o.f()這種情況下this會(huì)指向?qū)ο?code>o所以o.f()被調(diào)用返回37。

var o = {
    prop: 37
};

function indep() {
    return this.prop;
}
o.f = indep;
console.log(o.f());//37

定義對象o只有一個(gè)屬性prop賦值為37
這里買你有一個(gè)獨(dú)立的indep函數(shù),函數(shù)體return一個(gè)this.prop;如果直接去調(diào)用indep()那么這個(gè)this會(huì)指向window。

如果用賦值方式o.fo對象創(chuàng)建屬性f值為indep函數(shù)對象,那么這樣去調(diào)用我們?nèi)稳荒軌蚰玫?7,console.log(o.f());//37。

對象原型鏈上的this

Object.create({x:1});是系統(tǒng)內(nèi)置的函數(shù),這個(gè)函數(shù)會(huì)接收一個(gè)參數(shù),一般是一個(gè)對象。他會(huì)返回一個(gè)新創(chuàng)建的對象,并且讓這個(gè)對象的原型指向參數(shù),參數(shù)一般是個(gè)對象。

var o = {
    f: function() {
        return this.a + this.b;
    }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p);

JavaScript 精粹 基礎(chǔ) 進(jìn)階(6)函數(shù)和作用域(函數(shù)、this)

原型鏈上有f方法

var o = {
    f: function() {
        return this.a + this.b;
    }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f());//5

創(chuàng)建o對象屬性f值是一個(gè)函數(shù)對象,

通過var p = Object.create(o);創(chuàng)建了一個(gè)對象p,這個(gè)對象p是一個(gè)空的對象,并且他的原型會(huì)指向o,使用p.a給對象p添加屬性值為1p.b值為4,這樣就創(chuàng)建在對象上的屬性,那么我去調(diào)用p原型上的方法的時(shí)候,this.a + this.b;任然能取到對象上的a和b。

get/set方法與this

function modulus() {
    return Math.sqrt(this.re * this.re + this.im * this.im);
};

var o = {
    re: 1,
    im: -1,
    get phase() {
        return Math.atan2(this.im, this.re);
    }
};

Object.defineProperty(o, 'modulus', {
    get: modulus,
    enumerable: true,
    configurable: true
});

console.log(o.phase, o.modulus);//-0.7853981633974483 1.4142135623730951

構(gòu)造器中的this

function MyClass() {
    this.a = 23;
};
var o = new MyClass();
console.log(o.a); //23

如果正常去調(diào)用一個(gè)函數(shù)的話MyClass()那么這個(gè)函數(shù)的this會(huì)指向window但是如果我用new實(shí)例化構(gòu)造函數(shù)去調(diào)用,那么這里面的this會(huì)指向這樣一個(gè)空的對象,并且這個(gè)對象的原型會(huì)指向MyClass.prototype
最后這個(gè)this.a會(huì)作為返回值,因?yàn)檫@里沒有return所以默認(rèn)this會(huì)作為返回值,所以這個(gè)對象o就會(huì)就會(huì)有a屬性值23。

function c2() {
    this.a = 23;
    return {
        a: 24
    };
}
var f = new c2();
console.log(f.a); //24

c2函數(shù)this等于23,但是這一次return返回了一個(gè)對象,那么這個(gè)a就不再是23了,而是返回的這個(gè)對象,o.a屬性就變成了24。

如果是作為構(gòu)造器,外部使用new去調(diào)用的話,如果沒有return語句,或者return是基本類型的話,那么會(huì)將this作為返回

call/apply方法與this

function add(c, d) {
    return this.a + this.b + c + d;
};

var o = {
    a: 1,
    b: 3
};

var h=add.call(o, 5, 7);//1+3+5+7
console.log(h)

add.apply(o, [10, 20]);//1+3+10+20

function bar() {
    console.log(Object.prototype.toString.call(this));
};

bar.call(7);//[object Number]

函數(shù)add,這里面返回this.a + this.b + c + d;把這四個(gè)數(shù)相加起來,定義o對象有兩個(gè)屬性a和b,通過對象call方法,.call(o, 5, 7)第一個(gè)參數(shù)是你想作為this的這樣一個(gè)對象,5和7是想要添加的參數(shù),5賦值給function add(c, d)的c,7賦值給d;最終的結(jié)果就是1+3+5+7
add.call(o, 5, 7)add.apply(o, [10, 20])基本沒什么差別,只是apply是數(shù)組作為傳參的。

bind方法與this

function f() {
    return this.a;
};

var g = f.bind({
    a: "test"
});
console.log(g()); //test

var o = {
    a: 23,
    f: f,
    g: g
};
console.log(o.f(), o.g()); //23 "test"

在EcmaScript5中擴(kuò)展了叫bind的方法(IE6,7,8不支持)

這里有個(gè)函數(shù)對象f,通過bind方法,發(fā)現(xiàn)f.bind({ a: "test"});有一個(gè)參數(shù),這個(gè)參數(shù)是一個(gè)對象,這個(gè)對象就是你想要將某一個(gè)對象作為this的時(shí)候那就把這樣一個(gè)對象穿進(jìn)去,那么我們拿到一個(gè)新的g對象,新的g對象在調(diào)用的時(shí)候this已經(jīng)指向了bind的參數(shù),這對于我們需要去綁定一次重復(fù)調(diào)用仍然實(shí)現(xiàn)綁定,這樣會(huì)比call,apply會(huì)更加高效點(diǎn)。

還可以看到,這里有一個(gè)o然后把a(bǔ)賦值為23,f屬性賦值為f函數(shù),g賦值綁定之后的方法,輸出f的時(shí)候能拿到23,一般的函數(shù)根據(jù)調(diào)用方式來判斷,他是通過對象屬性去調(diào)用的,那么這里買你的this就會(huì)指向o那么就會(huì)拿到a:23。這里面比較特殊的我使用bind的方法去綁定了之后,即使我們把 新綁定之后的方法作為對象的屬性去調(diào)用,仍然會(huì)按照之前的綁定去走。所以返回"test"

[JavaScript]函數(shù)屬性arguments

函數(shù)屬性 & arguments

接觸函數(shù)屬性和方法

function foo(a, b, d) {
    console.log(arguments);
    console.log(arguments.length); //2
    console.log(arguments[0]); //1
    arguments[1] = 10;
    console.log(b); //change to 10;

    arguments[2] = 100;
    console.log(d); //即使手動(dòng)賦值任然是undefined. 沒有賦值arguments[2]=100;d等于undefined
    console.log(arguments.callee === foo);
}
foo(1, 2);
console.log(foo.length);
console.log(foo.name);
console.dir(foo);

JavaScript 精粹 基礎(chǔ) 進(jìn)階(6)函數(shù)和作用域(函數(shù)、this)

定義函數(shù)foo
通過foo.name獲取函數(shù)名
通過foo.length獲取函數(shù)行參個(gè)數(shù)
通過arguments.length獲取函數(shù)實(shí)際傳參的個(gè)數(shù)

有一個(gè)問題。foo(1, 2);傳參只有兩個(gè)參數(shù),實(shí)際上d是沒有傳進(jìn)來的, 這種情況嘗試去arguments[2] = 100;去賦值的時(shí)候?qū)?yīng)的d仍然是undefined

嚴(yán)格模式下

function foo(a, b, d) {
    'use strict'
    console.log(arguments.length); //2
    console.log(arguments[0]); //1
    arguments[1] = 10;
    console.log(b); //嚴(yán)格模式下仍然是1

    arguments[2] = 100;
    console.log(d); //即使手動(dòng)賦值任然是undefined. 沒有賦值arguments[2]=100;d等于undefined
    console.log(arguments.callee === foo);  //嚴(yán)格模式下arguments.callee是禁止使用
}
foo(1, 2);
console.log(foo.length);
console.log(foo.name);

apply/call方法

function foo(x, y) {
    console.log(x, y, this);
};
foo.call(100, 1, 2);                    //1 2 Number {[[PrimitiveValue]]: 100}
foo.apply(true, [3, 4]);                //3 4 Boolean {[[PrimitiveValue]]: true}
foo.apply(null);                        //undefined undefined Window {...}
foo.apply(undefined);                   //undefined undefined Window {...}

定義foo函數(shù),有x,y兩個(gè)屬性,輸出參數(shù)和對應(yīng)的this

對于call()來講第一個(gè)參數(shù)就是想作為this的對象如果不是對象他會(huì)轉(zhuǎn)成對象,所以這里的foo.call(100, 1, 2);100會(huì)轉(zhuǎn)成對應(yīng)的包裝類Number值為100。類似的foo.apply(true, [3, 4]);true會(huì)轉(zhuǎn)換成布爾值。
如果第一個(gè)參數(shù)是null,undefined的話,那么this會(huì)指向全局對象,對于瀏覽器就是window對象

需要注意在嚴(yán)格模式下

function foo(x, y) {
    'use strict';
    console.log(x, y, this);
};
foo.call(100, 1, 2);                    //1 2 100
foo.apply(true, [3, 4]);                //3 4 true
foo.apply(null);                        //undefined undefined null
foo.apply(undefined);                   //undefined undefined undefined

在嚴(yán)格模式下this輸出直接量

bind方法

在EcmaScript5中擴(kuò)展了叫bind的方法(IE6,7,8不支持)

this.x = 9; //創(chuàng)建了一個(gè)全局變量x并賦值為9
var mod = {
    x: 81,
    getX: function() {
        return this.x;
    }
};

console.log(mod.getX()); //81

var gitX = mod.getX;
console.log(gitX());; //9

var bound = gitX.bind(mod);
console.log(bound());//81

創(chuàng)建了一個(gè)全局變量x并賦值為9,然后有一個(gè)變量mod賦值為一個(gè)對象字面量,里面有x屬性 值為81,有一個(gè)getX屬性,值是一個(gè)匿名函數(shù),他會(huì)返回this.x

如果對象(點(diǎn))屬性名 mod.getX()這樣的調(diào)用方式。會(huì)返回這個(gè)對象為this返回81。

var gitX = mod.getX;如果定義一個(gè)全局變量gitX賦值為mod對象的getX屬性,這個(gè)時(shí)候this就會(huì)指向全局對象,也就是this.x = 9;所以返回9,console.log(gitX());; //9

通過bind方法改變函數(shù)運(yùn)行時(shí)的this指向,var bound = gitX.bind(mod);也就是說gitX函數(shù)在運(yùn)行時(shí)thismod對象。所以再次輸出81。

bind與currying 科里化功能

函數(shù)科里化就是把一個(gè)函數(shù)拆成多個(gè)單元。

function add(a,b,c){
    return a+b+c;
};

var func=add.bind(undefined,100);
func(1,2);//103

var func2=func.bind(undefined,200);
func2(10);//310

這里有一個(gè)函數(shù)add他的作用是把參數(shù)abc相加然后作為返回值,

可能有的時(shí)候不需要一次把函數(shù)都調(diào)用完,而是我調(diào)用一次把前面兩個(gè)參數(shù)傳完了后,得到這樣的函數(shù)我再去調(diào)用,傳入第三個(gè)值,比如通過add.bind()方法這次不需要改變this,那寫個(gè)undefined,提高額外參數(shù)100,這個(gè)我們拿到bind函數(shù)以后,相當(dāng)于這個(gè)100就會(huì)固定賦值給a第一個(gè)參數(shù),在調(diào)用func(1,2);傳入1和2的時(shí)候,1就會(huì)給b,2就會(huì)給c,所以最后的答案就是103。

類似的再去func.bind()也就是說綁定了兩個(gè)參數(shù)ab,b傳入200,再去調(diào)用一次func2(10)傳入10,那么就是100+200+10,最終結(jié)果就是310。

實(shí)際例子

JavaScript 精粹 基礎(chǔ) 進(jìn)階(6)函數(shù)和作用域(函數(shù)、this)

bind與new

function foo() {
    this.b = 100;
    return this.a;
};

var func = foo.bind({
    a: 1
});
console.log(func()); //1

var func2 = new func();
console.log(func2); //{b: 100}

foo函數(shù)聲明,函數(shù)體this.b=100return this.a;那如果我直接調(diào)用的話,this.b=100中的this是指向全局對象,所以說相當(dāng)于創(chuàng)建了一個(gè)全部變量b并賦值為100,返回全局對象上的a屬性。

使用bind()方法,var func = foo.bind({ a: 1 });來傳入一個(gè)參數(shù),一個(gè)字面量對象只有一個(gè)屬性a,直接調(diào)用的話func(),那么foo函數(shù)中的this就會(huì)指向bind({a:1})的參數(shù)。所以調(diào)用func()返回1。

如果使用new的話就特殊些,使用new除非func()函數(shù)return 除非是對象如果不是對象將會(huì)把this作為返回值,并且this會(huì)被初始化成一個(gè)默認(rèn)的空對象,這個(gè)空對象的原型是foo.prototype,所以使用new去調(diào)用的時(shí)候var func2 = new func();,即使用了var func = foo.bind({ a: 1 });bind方法但是這個(gè)this任然會(huì)指向沒有bind的時(shí)候的一樣,所以返回值會(huì)忽略return,返回this對象。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI