欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript中函數(shù)的四種調(diào)用方式總結(jié)

 更新時(shí)間:2023年10月30日 14:02:28   作者:Knockkk  
這篇文章主要為大家詳細(xì)介紹了JavaScript中函數(shù)的四種調(diào)用方式,文中的示例代碼講解詳細(xì),對我們深入掌握J(rèn)avaScript有一定的幫助,需要的可以參考下

在《JavaScript忍者秘籍》書中提到,我們有四種不同的方式進(jìn)行函數(shù)調(diào)用:

  • 作為一個(gè)函數(shù)進(jìn)行調(diào)用,這是最簡單的形式。
  • 作為一個(gè)方法進(jìn)行調(diào)用,在對象上進(jìn)行調(diào)用,支持面向?qū)ο缶幊獭?/li>
  • 作為構(gòu)造器進(jìn)行調(diào)用,創(chuàng)建一個(gè)新對象。
  • 通過apply() 或call() 方法進(jìn)行調(diào)用。

作為一個(gè)曾經(jīng)的小鎮(zhèn)做題家,一些關(guān)鍵詞自然而然就冒出來了,this、閉包、原型......這些概念都不陌生,但總感覺有些散亂。我發(fā)現(xiàn),順著書中這幾種函數(shù)調(diào)用方式,層層遞進(jìn),倒是可以幫助更好的理解和串聯(lián)這些知識點(diǎn)。

1. 作為函數(shù)調(diào)用

這里指的就是最簡單直接的調(diào)用方式:

function sum(a, b) {
  return a + b;
}

sum(1, 2)

函數(shù)封裝了一段邏輯,給一個(gè)輸入,得到一個(gè)輸出。函數(shù)最大的好處是它是可以復(fù)用的,而不是在每個(gè)需要的地方都寫一段重復(fù)的代碼。用好函數(shù)往往可以讓代碼變得更清晰。

使用函數(shù)時(shí)必然要關(guān)注它的作用域。簡單來說,函數(shù)內(nèi)是局部變量,只能在這個(gè)函數(shù)中訪問,而全局變量可以在程序的任何代碼中訪問。但在JS中比這復(fù)雜,因?yàn)楹瘮?shù)是可以嵌套的,也可以作為參數(shù)傳遞。比如下面的例子:

function outer() {
  let value = 0;

  function inner(n) {
    value += n;
    return value;
  }

  return inner;
}

const fn = outer();
fn(1); // 1
fn(2); // 3

此時(shí)inner()作為內(nèi)部函數(shù)是可以訪問到外部函數(shù)outer()內(nèi)定義的局部變量的,即使它們是兩個(gè)不同的函數(shù)。另外,正常來說,一個(gè)函數(shù)執(zhí)行完成后,所有函數(shù)內(nèi)定義的局部變量都會被回收,但這里不同:outer()執(zhí)行完成了,返回了一個(gè)函數(shù),而這個(gè)函數(shù)內(nèi)仍然能訪問value這個(gè)局部變量(value并沒有被回收)——即“閉包”。因?yàn)镴S支持更靈活的函數(shù)使用,所以也就引出了這些概念,這在Go語言中也是一摸一樣的。

2. 作為方法進(jìn)行調(diào)用

即函數(shù)作為對象的一個(gè)屬性,如下所示:

const dog = {
  name: "wangcai",
  age: 1,
  sayHi: function () {
    console.log("Hello~");
  },
};

dog.sayHi(); // Hello~

JS中可以用對象表示一些屬性的集合,比如一只狗有名字和年齡。當(dāng)然這個(gè)對象也可以有方法,比如這只狗會打招呼。如果想在打招呼的時(shí)候報(bào)出自己的名字怎么做呢?當(dāng)然我們可以直接用對象名訪問,即dog.name,但還有一種更友好的方式——this(在Java中,關(guān)鍵字“this”表示當(dāng)前對象的引用,而在JS中,“this”是支持面向?qū)ο缶幋a的主要手段之一)。

const dog = {
  name: "wangcai",
  age: 1,
  sayHi: function () {
    console.log("Hello,my name is ", this.name);
  },
};

dog.sayHi(); // Hello,my name is  wangcai

這樣就清晰多了,不用管變量名的dog是哪個(gè)dog,this就是本狗了。那普通函數(shù)不在對象中,它調(diào)用時(shí)的this是什么呢?

function getThis() {
  return this;
}

getThis() === window // true

在瀏覽器中,答案就是全局環(huán)境window。回到前面dog的例子,下面代碼中this又是什么呢?是本狗嗎?答案我們在后面揭曉。

const dog = {
  name: "wangcai",
  age: 1,
  sayHi: function () {
    console.log("Hello,my name is ", this.name);
  },
};

const sayHi = dog.sayHi;

sayHi();

3. 作為構(gòu)造器進(jìn)行調(diào)用

如果我有很多條狗,那么用對象來一個(gè)個(gè)聲明就略顯笨拙了。你可能會寫一個(gè)createDog的函數(shù),來按模式批量生產(chǎn)dog。

function createDog(name, age) {
  return {
    name,
    age,
    sayHi: function () {
      console.log("Hello,my name is ", this.name);
    },
  };
}
const dog = createDog("wangcai", 1);

但同樣,有一種更“面向?qū)ο?rdquo;,更友好的方式——構(gòu)造函數(shù)(constructor,ES6的“class”就是構(gòu)造函數(shù)的語法糖) 。

function Dog(name, age) {
  this.name = name;
  this.age = age;
  this.sayHi = function () {
    console.log("Hello,my name is ", this.name);
  };
}

const dog = new Dog("wangcai", 1);

構(gòu)造函數(shù)需要搭配“new”關(guān)鍵字使用,它將自動返回一個(gè)新的對象。另外,構(gòu)造函數(shù)一般用大寫開頭,同時(shí)它應(yīng)該是一個(gè)名詞,而不是動詞。

構(gòu)造函數(shù)只是一個(gè)約定,語言本身并沒有限制你如何使用它。你可以直接執(zhí)行Dog("wangcai",i),但這樣沒有什么意義,還會產(chǎn)生一些意外的全局變量。

我們可以結(jié)合前一節(jié)的“對象方法”來理解構(gòu)造器的調(diào)用過程:

  • 創(chuàng)建一個(gè)空對象
  • 在該對象上執(zhí)行這個(gè)函數(shù)(函數(shù)調(diào)用時(shí)的this指向這個(gè)對象)
  • 最后將這個(gè)對象返回,如果沒有顯式的返回值(還差了一個(gè)步驟,暫時(shí)沒涉及,后面再說)

JS中幾乎所有對象都有自己的構(gòu)造函數(shù),對于使用字面量語法聲明的對象,它的構(gòu)造函數(shù)就是Object。

const dog = { name: "wangcai" };

JS中有三種方式創(chuàng)建一個(gè)對象。

  • 字面量語法,如const obj = { value: 100 }
  • Object.create()
  • 使用new調(diào)用構(gòu)造函數(shù)初始化一個(gè)對象

只有通過Object.create(null)創(chuàng)建的對象是沒有構(gòu)造函數(shù)的,也沒有“原型”。

對象與構(gòu)造函數(shù)之間通過原型進(jìn)行關(guān)聯(lián)。還是以我們的dog為例:

const dog = new Dog("wangcai", 1);

Object.getPrototypeOf(dog) === Dog.prototype // true

“當(dāng)構(gòu)造函數(shù)搭配new使用時(shí),該函數(shù)的prototype數(shù)據(jù)屬性將用作新對象的原型。默認(rèn)情況下,函數(shù)的prototype是一個(gè)普通的對象。這個(gè)對象具有一個(gè)屬性:constructor,它是對這個(gè)函數(shù)本身的一個(gè)引用。 constructor 屬性是可編輯、可配置但不可枚舉的”。所以,我們通過Object.getPrototypeOf(dog).constructor可以直接獲取到對象的構(gòu)造函數(shù)。下面就是瀏覽器控制臺打印出的dog對象:

那對象的原型又是什么呢?它是JS中一種獨(dú)特的機(jī)制,它的特點(diǎn)如下:

  • 每個(gè)對象都有一個(gè)私有屬性指向另一個(gè)名為原型(prototype)的對象。當(dāng)訪問一個(gè)對象屬性時(shí),如果屬性不存在,就會繼續(xù)查找這個(gè)對象的原型屬性。
  • 原型對象也有一個(gè)自己的原型,層層向上直到一個(gè)對象的原型為null。Object.prototype的原型始終為null且不可更改。

所以,一個(gè)對象不僅有實(shí)例屬性,還有原型屬性,它們都可以被訪問到,只是原型屬性是不可枚舉的。

const o = { value: 100};

Object.keys(o); // ['value']

o.valueOf(); // valueOf是構(gòu)造函數(shù)Object的prototype上定義的方法,可以正常訪問

再回顧new進(jìn)行初始化的過程,是缺了什么步驟呢?就是將對象的原型指向構(gòu)造函數(shù)的prototype。我們可以按這個(gè)規(guī)則模擬一個(gè)new函數(shù)。

function Dog(name, age) {
  this.name = name;
  this.age = age;
}

Dog.prototype.sayHi = function () {
  console.log("Hello,my name is ", this.name);
};

function myNew(fn, ...args) {
  const obj = Object.create(fn.prototype); // 創(chuàng)建一個(gè)對象,將對象的原型指向fn.prototype
  const res = fn.apply(obj, args);

  return res ?? obj;
}

const dog = myNew(Dog, "wangcai", 1);

dog.sayHi(); // Hello,my name is wangcai

Object.getPrototypeOf(dog) === Dog.prototype; // true

大家細(xì)心會發(fā)現(xiàn),這個(gè)例子中將sayHi函數(shù)放在了Dog的prototype對象上,而不像之前在函數(shù)內(nèi)通過this.sayHi聲明。兩種方式new出來的對象都是能調(diào)用sayHi()的,唯一的區(qū)別是:一個(gè)是通過對象的實(shí)例屬性訪問,而另一個(gè)是通過原型屬性訪問。后者看起來是這樣的:

原型屬性具有一些優(yōu)點(diǎn),比如在這個(gè)例子中,每個(gè)new出來的對象訪問的都是同一個(gè)sayHi函數(shù)(定義在Dog的prototype對象上),而不是重新拷貝,這樣節(jié)省內(nèi)存。同時(shí),sayHi函數(shù)只需修改一次,就能應(yīng)用到所有實(shí)例化的對象中。

Dog.prototype.sayHi = function () {
  console.log("Good morning~");
};

dog.sayHi(); // Good morning~

原型鏈的特性還能支持我們實(shí)現(xiàn)對象的“繼承”。首先我們用class語法試下繼承的效果。

// 基類
class Animal {
  constructor(name) {
    this.name = name;
  }

  eat() {
    console.log(`${this.name} is eating`);
  }
}

//派生類
class Dog extends Animal {
  constructor(name, age) {
    super(name);
    this.age = age;
  }

  sayHi() {
    console.log(`Hello, my name is ${this.name}, I am ${this.age} years old`);
  }
}

const dog = new Dog("wangcai", 1);

dog.sayHi(); // Hello, my name is wangcai, I am 1 years old
dog.eat(); // wangcai is eating

分析一下,agename都在dog對象的實(shí)例屬性上,而sayHi() 函數(shù)在dog對象的原型上,即Dog.prototype。再往上一層,Dog.prototype這個(gè)對象的原型是Animal.prototype,Animal.prototype上具有eat()方法。那再往上一層呢?Animal.prototype這個(gè)對象的原型是Object,再往上就到原型鏈頂端null了。

接下來相信大家也有概念怎么手動實(shí)現(xiàn)繼承了。下面是一個(gè)示例:

function Animal(name) {
  this.name = name;
}

Animal.prototype.eat = function () {
  console.log(`${this.name} is eating`);
};

function Dog(name, age) {
  this.age = age;
  Animal.call(this, name); // 將Animal的實(shí)例屬性放到Dog對象上

  Object.setPrototypeOf(Dog.prototype, Animal.prototype); // 設(shè)置原型鏈
}

Dog.prototype.sayHi = function () {
  console.log(`Hello, my name is ${this.name}, I am ${this.age} years old`);
};

const dog = new Dog("wangcai", 1);

dog.sayHi();
dog.eat();

4. 通過apply()或call()方法進(jìn)行調(diào)用

const dog = {
  name: "wangcai",
  age: 1,
  sayHi: function () {
    console.log("Hello,my name is ", this.name);
  },
};

const sayHi = dog.sayHi;

sayHi();

再回顧前面的例子,揭曉答案,結(jié)果是Hello,my name is undifined,顯然不是本狗了。

為什么會這樣呢?因?yàn)镴S中函數(shù)的“this”是由調(diào)用點(diǎn)決定的(在運(yùn)行時(shí)確定) ,而不是在函數(shù)聲明處決定。這就讓人很困擾了,本狗的名字都對不上了,這樣真的好嗎?其實(shí)這樣是為了提供更大的靈活性——支持動態(tài)上下文。

function sayHi() {
  console.log("Hello,my name is ", this.name);
}

const dog = {
  name: "wangcai",
  sayHi,
};

const cat = {
  name: "miaomiao",
  sayHi,
};

dog.sayHi(); // Hello,my name is wangcai
cat.sayHi(); // Hello,my name is miaomiao

通過允許this由調(diào)用點(diǎn)動態(tài)確定,可以讓同一個(gè)函數(shù)在不同的對象上使用。另外,JS也提供了顯式指定函數(shù)執(zhí)行時(shí)的this為某個(gè)對象的方法,即apply()call()。

const dog = {
  name: "wangcai",
  age: 1,
  sayHi: function () {
    console.log("Hello,my name is ", this.name);
  },
};

const sayHi = dog.sayHi;

sayHi.apply(dog); // Hello,my name is  wangcai
sayHi.call(dog); // Hello,my name is  wangcai

很開心,本狗又回來了。apply()call()只有函數(shù)傳參上的差異,在使用時(shí)看哪個(gè)方便用哪個(gè)就行。

總結(jié)

通過體驗(yàn)函數(shù)這四種不同的調(diào)用方式,我們逐漸接觸了JS中一些底層的知識點(diǎn):作為函數(shù)調(diào)用時(shí)的閉包,作為方法調(diào)用時(shí)的this指向,作為構(gòu)造器調(diào)用時(shí)的原型。JS中的函數(shù)非常靈活,也很強(qiáng)大,并且與對象有著密切的聯(lián)系。

以上就是JavaScript中函數(shù)的四種調(diào)用方式總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript函數(shù)調(diào)用的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • TypeScript中類型映射的使用

    TypeScript中類型映射的使用

    TypeScript中的映射類型和數(shù)學(xué)中的映射類似,能夠?qū)⒁粋€(gè)集合的元素轉(zhuǎn)換為新集合的元素,本文就來介紹一下TypeScript中類型映射的使用,感興趣的可以了解一下
    2023-10-10
  • JS原生瀑布流效果實(shí)現(xiàn)

    JS原生瀑布流效果實(shí)現(xiàn)

    這篇文章主要介紹了JS原生瀑布流效果實(shí)現(xiàn) ,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • js讓彈出新窗口居中顯示的腳本

    js讓彈出新窗口居中顯示的腳本

    我們經(jīng)常需要在新窗口彈出頁面并需要居中顯示
    2008-01-01
  • IE和FireFox(FF)中js和css的不同

    IE和FireFox(FF)中js和css的不同

    整理了一點(diǎn)IE和FF下的不同點(diǎn),歡迎大家補(bǔ)充
    2009-04-04
  • JS Replace 全部替換字符的用法小結(jié)

    JS Replace 全部替換字符的用法小結(jié)

    本篇文章主要是對JS Replace 全部替換字符的用法進(jìn)行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2013-12-12
  • 使用mock.js隨機(jī)數(shù)據(jù)和使用express輸出json接口的實(shí)現(xiàn)方法

    使用mock.js隨機(jī)數(shù)據(jù)和使用express輸出json接口的實(shí)現(xiàn)方法

    這篇文章主要介紹了使用mock.js隨機(jī)數(shù)據(jù)和使用express輸出json接口的實(shí)現(xiàn)方法,需要的朋友可以參考下
    2018-01-01
  • ES6學(xué)習(xí)筆記之Set和Map數(shù)據(jù)結(jié)構(gòu)詳解

    ES6學(xué)習(xí)筆記之Set和Map數(shù)據(jù)結(jié)構(gòu)詳解

    這篇文章主要介紹了ES6學(xué)習(xí)筆記之Set和Map數(shù)據(jù)結(jié)構(gòu),結(jié)合實(shí)例形式詳細(xì)分析了ECMAScript中基本數(shù)據(jù)結(jié)構(gòu)Set和Map的常用屬性與方法的功能、用法及相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2017-04-04
  • JavaScript中Number的對象解析

    JavaScript中Number的對象解析

    這篇文章主要介紹了JavaScript中Number的對象解析,Number對象是數(shù)值對應(yīng)的包裝對象,可以作為構(gòu)造函數(shù)使用,也可以作為工具函數(shù)使用,感興趣的朋友可以參考一下下面文章內(nèi)容
    2022-08-08
  • 如何使用CocosCreator對象池

    如何使用CocosCreator對象池

    這篇文章主要介紹了CocosCreator對象池,對性能有研究的同學(xué),要著重看一下
    2021-04-04
  • uni-app調(diào)取接口的3種方式以及封裝uni.request()詳解

    uni-app調(diào)取接口的3種方式以及封裝uni.request()詳解

    我們在實(shí)際工作中要將數(shù)據(jù)傳輸?shù)椒?wù)器端,從服務(wù)器端獲取信息,都是通過接口的形式,下面這篇文章主要給大家介紹了關(guān)于uni-app調(diào)取接口的3種方式以及封裝uni.request()的相關(guān)資料,需要的朋友可以參考下
    2022-08-08

最新評論