Rumah hujung hadapan web tutorial js 深入理解JavaScript作用域

深入理解JavaScript作用域

Feb 21, 2017 pm 03:31 PM
javascript Skop faham

1.png

引言

JavaScript是门全栈性的语言,尤其是在2016年,经常听到JavaScript要一统天下的梗,甚至有流言说16年会个Vue.js就能找到工作,和当年iOS会个TableView就能找工作一样.(tableView就相当于Android的ListView,不过现在基本都用RecyclerView了)

2016年前端的热门技术基本都和JavaScript有关,比如移动端跨平台的Facebook出品的React Native和阿里的Weex,热修复技术JSPath,以及后端的Node.js(本宝宝非常喜欢的一门技术栈).昨晚去gibhub看了下,Vue的star数量已经超过了jQuery,虽然star数量并不能证明些什么,但是至少我们已经看到,前端思想已经从之前的document操作到了数据驱动开发的转变(有兴趣的话我可以之后结合Android、iOS、Vue,用一个小demo演示一下这个思想转变),有些公司甚至开始尝试用饿了么出品的Element来替代EasyUI(做过后端的同学应该都知道EasyUI真的是AV画质....)

JS技术层出不穷,之前就有一篇很火的文章,2016年学JS是一种什么样的体验,瞬间吓尿了不少人.大家都把注意力放到了框架和新技术上,原生的JS反而遭到了冷落,所以想通过几个基础性的JS问题,和大家一起交流(更希望大家带我飞,但是别把我丢到马航上飞...)


JavaScript中的作用域

下面一个简单的问题:

<script>
    var str1 = "hello";
    var str2 = "world";

    function t1() {
        console.log(str1);
        console.log(str2);

        var str2 = "toby";
        console.log(str2);
    }

    //这里会输出什么?
    t1();

</script>
Salin selepas log masuk

这是一个很简单的JS作用域问题,但是你越是强调简单这两个字,就越容易使人放松警惕,所以导致有些同学不假思索的回答输出

● hello

● world

● toby

但是结果是输出

● hello

● undefined

● toby

那么这就奇怪了,为什么会有undefined呢,不是应该是world吗?首先我们要明白,变量的寻找会遵循就近原则,所以js会先在函数中找,找不到才会向外找,而函数内有str2,但是运行到console.log(str2)时str2未定义,所以就出现了undefined


词法分析

知其然还必须其所以然,那么我们再来看几个例子

例子1

<script>
    function t(userName) {
        console.log(userName);//这里输出什么?

        function userName() {
            console.log(&#39;tom&#39;);
        }
    }
    t(&#39;toby&#39;);
</script>
Salin selepas log masuk

输出的结果是什么,这个例子好像和上面不一样,有种回到高中数学,题型一变就懵逼的感觉,这个时候可能有些同学会觉得是toby,但是实际输出是

function userName() {
    console.log(&#39;tom&#39;);
}
Salin selepas log masuk

为什么是function呢?其实这种作用域的问题,都是可以通过"套公式"来得出,这个公式,就是JS中的词法分析,JS中函数执行前,必须要做的一项工作就是词法分析,那么究竟要什么什么呢?分析参数,分析变量声明,分析函数声明,那么我们就拿这道题来套一下公式

执行t('toby')的时候,会开始两个阶段,一个是分析阶段,分析完就到执行阶段

分析阶段:

● 函数运行的瞬间,会生成一个Active Object对象(以下简称AO对象),一个函数作用域内能找到的所有变量,都在AO上,此时用代码表示为: t.AO = {}

● 分析参数: 接收参数,以参数名为属性,参数值为属性值,因为没有参数,因此分析结果用代码表示为: t.AO = {userName : toby}

● 分析var声明: t函数内没有var声明,略过

● 分析函数声明: 这个函数声明有个特点,AO上如果有与函数名同名的属性,则会被此函数覆盖,因为函数在JS领域,也是变量的一种类型,因此用代码表示为: t.AO = { userName : function userName() {console.log('tom');}}

执行阶段:

执行t('toby')的时候,当执行到console.log(userName)时,就调用t.AO.userName,所以,最后的输出结果是function userName() {console.log('tom');}

例子2

<script>
    function t(userName) {
        console.log(userName);//这里输出什么?

        var userName = function () {
            console.log(&#39;tom&#39;);
        }
    }
    t(&#39;toby&#39;);
</script>
Salin selepas log masuk

那这里的输出又是什么呢?这个好像又和上面的例子不一样,又再次陷入懵逼状态?别怕麻烦,坚定的按照公式再走一次流程(上面的的例子写得比较详细,下面的分析就简单写)

分析之前,首先要弄明白两个概念,一个叫函数声明,一个叫函数表达式

//这个叫函数声明
function userName() {
    console.log(&#39;tom&#39;);
}

//这个叫函数表达式
var userName = function () {
    console.log('tom');
}
Salin selepas log masuk

分析阶段:

● 创建AO对象,t.AO = {}

● 分析参数: t.AO = {userName : toby}

● 分析var声明: 在AO上,形成一个属性,以var的变量名为属性名,值为undefined,(因为是先分析,后执行,这只是词法分析阶段,并不是执行阶段,分析阶段值都是undefined,如果执行阶段有赋值操作,那值会按照正常赋值改变),也就是说代码应该表示为:t.AO = {userName : undefined},但是还有另外一个原则,那就是如果AO有已经有同名属性,则不影响(也就是什么事都不做),由于分析参数时,AO上已经有userName这个属性了,所以按照这个原则,此时什么事都不做,也就是说,此时按照分析参数时的结果t.AO = {userName : toby}

● 分析函数声明: 此时没有函数声明,略过

执行阶段:

调用t.AO.userName,所以,最后的输出结果是toby

例子3

<script>
    t();
    t2();

    function t() {
        console.log(&#39;toby&#39;);//这里会输出什么?
    }

    var t2 = function () {
        console.log(&#39;hello toby&#39;);//这里会输出什么?
    };
</script>
Salin selepas log masuk

那么我们再来看一个例子,这下彻底回到高中时代,做了两个例子好像感觉掌握了,结果考试你给来看这个?

答案是,t()输出为toby,t2()则会报错.这又是为什么?

● t()可以调用是因为在词法分析的过程,就已经完成了t函数的分析,所以可以调用

● t2()不能调用是因为在词法分析的阶段,分析到有一个t2声明,在AO上只是形成了一个属性,但是值为undefined

例子4

<script>
    function t(userName) {
        console.log(userName);//这里输出什么?
        function userName() {
            console.log(userName);//这里输出什么?
        }
        userName();
    }
    t(&#39;toby&#39;);
</script>
Salin selepas log masuk

函数里面套函数,这次竟然又和前面不一样了...这次我不说答案了,直接先套公式走一波

t('toby')的分析和执行阶段

分析阶段:

● 创建AO对象,t.AO = {}

● 分析参数: t.AO = {userName : toby}

● 分析var声明: 有同名属性,不做任何事,还是t.AO = {userName : toby}

● 分析函数声明: 有同名属性,覆盖: t.AO = {userName : function userName() {console.log(userName);}}

执行阶段: t.AO.userName 输出为function userName() {console.log(userName);}}

userName()的分析和执行阶段

这里也要搞清楚两个概念

//执行userName()分析的是
function () {
  console.log(userName);
};

//而不是
var userName = function () {
    console.log(userName);
};
Salin selepas log masuk

分析阶段:

● 创建AO对象,userName.AO = {}

● 分析参数: 无,略过

● 分析var声明: 无,略过

● 分析函数声明: 无,略过

执行阶段: 因为此时userName.AO = {}是个空对象,无法执行userName.AO.userName,所以会向上一层找,所以输出t.AO.userName的结果,也就是function userName() {console.log(userName);}}

例子5

<script>
    function t(userName) {
        console.log(userName);//这里输出什么?
        var userName = function () {
            console.log(userName);//这里输出什么?
        }
        userName();
    }
    t(&#39;toby&#39;);
</script>
Salin selepas log masuk

好吧,我保证这个是最后一道...这个输出结果是什么呢?我们只要坚定公式没问题,就一定能得出结果,那么再套公式走一波

t('toby')的分析和执行阶段

分析阶段:

● 创建AO对象,t.AO = {}

● 分析参数: t.AO = {userName : toby}

● 分析var声明: 有同名属性,不做任何事,还是t.AO = {userName : toby}

● 分析函数声明: 无,略过

执行阶段: 执行console.log(userName);时调用t.AO.userName 输出为toby,执行完后,代码继续往下执行,那么就到了进行var的赋值操作(var的分析和执行的区别看例子2中我有解释),此时t.AO = {userName : function userName() {console.log(userName);}}},代码继续往下执行,接着就执行到了userName()

userName()的分析和执行阶段

分析阶段:

● 创建AO对象,userName.AO = {}

● 分析参数: 无,略过

● 分析var声明: 无,略过

● 分析函数声明: 无,略过

执行阶段: 按照例子4我们知道userName.AO是个空对象,所以会往上调用t.AO.userName,所以输出为:function userName() {console.log(userName);}}}


总结

JavaScript作用域会先在自己的AO上找,找不到就到父函数的AO上找,再找不到再找上一层的AO,直到找到window.这样就形成一条链,这条AO链就是JavaScript中的作用域链.JavaScript中有两条很重要的链,一条是作用域链,一条是原型链,

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Penggunaan typedef struct dalam bahasa c Penggunaan typedef struct dalam bahasa c May 09, 2024 am 10:15 AM

typedef struct digunakan dalam bahasa C untuk mencipta alias jenis struktur untuk memudahkan penggunaan struktur. Ia alias jenis data baharu kepada struktur sedia ada dengan menentukan alias struktur. Faedah termasuk kebolehbacaan yang dipertingkatkan, penggunaan semula kod dan pemeriksaan jenis. Nota: Struktur mesti ditakrifkan sebelum menggunakan alias itu mestilah unik dalam program dan hanya sah dalam skop di mana ia diisytiharkan.

Bagaimana untuk menyelesaikan pembolehubah yang diharapkan dalam java Bagaimana untuk menyelesaikan pembolehubah yang diharapkan dalam java May 07, 2024 am 02:48 AM

Pengecualian nilai jangkaan pembolehubah dalam Java boleh diselesaikan dengan: memulakan pembolehubah menggunakan nilai nol menggunakan semakan dan tugasan;

Kelebihan dan kekurangan penutupan dalam js Kelebihan dan kekurangan penutupan dalam js May 10, 2024 am 04:39 AM

Kelebihan penutupan JavaScript termasuk mengekalkan skop pembolehubah, membolehkan kod modular, pelaksanaan tertunda, dan keburukan pengendalian peristiwa termasuk kebocoran memori, peningkatan kerumitan, overhed prestasi dan kesan rantaian skop.

Apa yang termasuk bermakna dalam c++ Apa yang termasuk bermakna dalam c++ May 09, 2024 am 01:45 AM

Arahan prapemproses #include dalam C++ memasukkan kandungan fail sumber luaran ke dalam fail sumber semasa, menyalin kandungannya ke lokasi yang sepadan dalam fail sumber semasa. Digunakan terutamanya untuk memasukkan fail pengepala yang mengandungi pengisytiharan yang diperlukan dalam kod, seperti #include <iostream> untuk memasukkan fungsi input/output standard.

Petunjuk pintar C++: analisis komprehensif kitaran hayat mereka Petunjuk pintar C++: analisis komprehensif kitaran hayat mereka May 09, 2024 am 11:06 AM

Kitaran hayat penunjuk pintar C++: Penciptaan: Penunjuk pintar dicipta apabila memori diperuntukkan. Pemindahan pemilikan: Pindahkan pemilikan melalui operasi pemindahan. Keluaran: Memori dikeluarkan apabila penunjuk pintar keluar dari skop atau dikeluarkan secara eksplisit. Pemusnahan objek: Apabila objek runcing dimusnahkan, penunjuk pintar menjadi penunjuk tidak sah.

Bolehkah definisi fungsi dan panggilan dalam C++ bersarang? Bolehkah definisi fungsi dan panggilan dalam C++ bersarang? May 06, 2024 pm 06:36 PM

boleh. C++ membenarkan definisi dan panggilan fungsi bersarang. Fungsi luaran boleh menentukan fungsi terbina dalam, dan fungsi dalaman boleh dipanggil terus dalam skop. Fungsi bersarang meningkatkan enkapsulasi, kebolehgunaan semula dan kawalan skop. Walau bagaimanapun, fungsi dalaman tidak boleh mengakses pembolehubah tempatan fungsi luaran secara langsung, dan jenis nilai pulangan mesti konsisten dengan pengisytiharan fungsi luaran Fungsi dalaman tidak boleh rekursif sendiri.

Perbezaan antara let dan var dalam vue Perbezaan antara let dan var dalam vue May 08, 2024 pm 04:21 PM

Dalam Vue, terdapat perbezaan dalam skop apabila mengisytiharkan pembolehubah antara let dan var: Skop: var mempunyai skop global dan let mempunyai skop peringkat blok. Skop peringkat blok: var tidak mencipta skop peringkat blok, mari buat skop peringkat blok. Pengisytiharan Semula: var membenarkan pembolehubah diisytiharkan semula dalam skop yang sama, jangan biarkan.

Terdapat beberapa situasi di mana ini dalam js menunjuk ke Terdapat beberapa situasi di mana ini dalam js menunjuk ke May 06, 2024 pm 02:03 PM

Dalam JavaScript, jenis penunjuk ini termasuk: 1. Objek global 2. Panggilan fungsi; 4. Pengendali acara; Selain itu, penunjuk kepada ini boleh ditetapkan secara eksplisit menggunakan kaedah bind(), call(), dan apply().

See all articles