谈谈javascript语法里一些难点题目(一)

1) 引子

前不久我竖立的手艺群里一名MM问了一个如许的题目,她贴出的代码以下所示:

var a = 1;

function hehe()

{

         window.alert(a);

         var a = 2;

         window.alert(a);

}

hehe();

实行效果以下所示:

第一个alert:

《谈谈javascript语法里一些难点题目(一)》

第二个alert:

《谈谈javascript语法里一些难点题目(一)》

这是一个使人惊讶的效果,为何第一个弹出框显现的是undefined,而不是1呢?这类迷惑的原理我形貌以下:

一个页面里直接定义在script标签下的变量是全局变量即属于window对象的变量,依据javascript作用域链的原理,当一个变量在当前作用域下找不到该变量的定义,那末javascript引擎就会沿着作用域链往上找直到在全局作用域里查找,按上面的代码所示,虽然函数内部从新定义了变量的值,然则内部定义之前函数运用了该变量,那末依据作用域链的原理在函数内部变量定义之前运用该变量,javascript引擎应当会在全局作用域里找到变量定义,而实际情况倒是变量未定义,这究竟是如何回事呢?

当时群里许多人都给出了题目标解答,我也给出了我本身的解答,实在这个题目良久之前我确实研讨过,然则刚被问起了我竟然照样有个卡壳期,在加上近来研讨javascriptMVC的写法,发明本身读代码时刻对new 、prototype、apply以及call的用法任然要体会半天,所以我以为有必要对javascript基础语法里比较难明白的题目做个梳理,实在写博客的一个很大的优点就是写出来的学问逻辑会比你在脑子里重复梳理的逻辑映像越发的深入。

   下面最先本文的主要内容,我会从基础学问一步步讲起。

2) Javascript的变量

Java言语里有一句很典范的话:在java的天下里,统统皆是对象

Javascript虽然跟java没有半点毛关联,然则许多会运用javascript的朋侪一样以为:在javascript的天下里,统统也皆是对象

实在javascript言语和java言语一样变量是分为两种范例:基础数据范例和援用范例。

基础范例是指:Undefined、Null、Boolean、Number和String;而援用范例是指多个指组成的对象,所以javascript的对象指的是援用范例。在java里能说统统是对象,是由于java言语里对一切基础范例都做了对象封装,而这点在javascript言语里也是一样的,所以提在javascript天下里统统皆为对象也不为过。

然则实际开辟里假如我们对基础范例和援用范例的区分不是很清楚,就会遇到我们许多不能明白的题目,下面我们来看看下面的代码:

var str = "sharpxiajun";

str.attr01 = "hello world";

console.log(str);//  运转效果:sharpxiajun

console.log(str.attr01);// 运转效果:undefined

运转之,我们发明作为基础数据范例,我们没法为这个变量增加属性,固然要领也一样不能够,例以下面的代码:

str.ftn = function(){

    console.log("str ftn");

}

str.ftn();

运转之,效果以下图所示:

《谈谈javascript语法里一些难点题目(一)》

当我们运用援用范例时刻,效果就和上面完整差别了,人人请看下面的代码:

var obj1 = new Object();

obj1.name = "obj1 name";

console.log(obj1.name);// 运转效果:obj1 name

javascript里的基础范例和援用范例的区分和其他言语相似,这是一个老调长谈的题目,然则在实际中许多人都明白它,然则却很难应用它去明白题目。

Javascript里的基础变量是存放在栈区的(栈区指内存里的栈内存),它的存储组织以下图所示:

《谈谈javascript语法里一些难点题目(一)》

《谈谈javascript语法里一些难点题目(一)》

javascript里援用变量的存储就比基础范例存储要复杂多,援用范例的存储须要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,以下图所示:

在javascript里变量的存储包括三个部份:

  • 部份二:栈区变量的值;部份一:栈区的变量标示符;
  • 部份二:栈区变量的值;
  • 部份三:堆区存储的对象。

变量差别的定义,这三个部份也会随之发作变化,下面我来枚举一些典范的场景:

场景一:以下代码所示:

var qqq;

console.log(qqq);// 运转效果:undefined

运转效果是undefined,上面的代码的范例诠释就是变量被定名了,然则还未初始化,此时在变量存储的内存里只具有栈区的变量标示符而没有栈区的变量值,固然更没有堆区存储的对象。

场景二:以下代码所示:

var qqq;

console.log(qqq);// 运转效果:undefined

console.log(xxx);

运转之,效果以下图所示:

《谈谈javascript语法里一些难点题目(一)》

会提醒变量未定义。在任何言语里变量未定义就运用都是违法的,我们看到javascript里也是云云,然则我们做javascript开辟时刻,常常有人会说变量未定义也是能够运用,如何我的例子里却不能运用了?那末我们看看下面的代码:

xxx = "outer xxx";

console.log(xxx);// 运转效果:outer xxx

function testFtn(){

    sss = "inner sss";

    console.log(sss);// 运转效果:outer sss

}

testFtn();

console.log(sss);//运转效果:outer sss

console.log(window.sss);//运转效果:outer sss

在javascript定义变量须要运用var症结字,然则javascript能够不运用var预先定义好变量,在javascript我们能够直接赋值给没有被var定义的变量,不过此时你这么操纵变量,不论这个操纵是在全局作用域里照样在部分作用域里,变量终究都是属于window对象,我们看看window对象的组织,以下图所示:

《谈谈javascript语法里一些难点题目(一)》

由这两个场景我们能够晓得在javascript里的变量不能一般运用即报出“xxx is not defined”毛病(这个毛病下,后续的javascript代码将不能一般运转)只要当这个变量既没有被var定义同时也没有举行赋值操纵才会发作,而只要赋值操纵的变量不论这个变量在谁人作用域里举行的赋值,这个变量终究都是属于全局变量即window对象。

由上面我枚举的两个场景我们来明白下引子里网友提出的题目,下面我修正一下代码,以下所示:

//var a = 1;

function hehe()

{

    console.log(a);

    var a = 2;

    console.log(a);

}

hehe();

效果以下图所示:

《谈谈javascript语法里一些难点题目(一)》

我再改下代码:

//var a = 1;

function hehe()

{

    console.log(a);

   // var a = 2;

    console.log(a);

}

hehe();

运转之,效果以下所示:

《谈谈javascript语法里一些难点题目(一)》

对照两者代码以及引子里的代码,我们发明题目标症结是var a=2所引发的。在代码一里我解释了全局变量的定义,效果和引子里代码的效果一致,这申明函数内部a变量的运用和全局环境是无关的,代码二里我解释了症结代码var a = 2,代码运转效果发作了变化,递次报错了,确实很让人疑心,疑心的地方在于部分作用域里变量定义的位置在变量第一次运用以后,然则递次没有报错,这不相符javascript变量未定义既要报错的原理。

实在这个变量任然被定义即内存存储里有了标示符,只不过没有被赋值,代码一则申明,内部变量a已和外部环境无关,如何回事?假如我们依据代码运转是依据递次实行的逻辑来明白,这个代码也就没法明白。

实在javascript里的变量和其他言语有很大的差别,javascript的变量是一个松懈的范例,松懈范例变量的特性是变量定义时刻不须要指定变量的范例,变量在运转时刻能够随便转变数据的范例,然则这类特征并不代表javascript变量没有范例,当变量范例被肯定后javascript的变量也是有范例的。然则在实际中,许多递次员把javascript松懈范例明白为了javascript变量是能够随便定义即你能够不必var定义,也能够运用var定义,实在在javascript言语里变量定义没有运用var,变量必需有赋值操纵,只要赋值操纵的变量是给予给window,这实际上是javascript言语设计者提拔javascript安全性的一个做法。

另外javascript言语的松懈范例的特性以及运转时刻随时更转变量范例的特性,许多递次员会以为javascript变量的定义是在运转期举行的,更有甚者有些人以为javascript代码只要运转期,实在这类明白是毛病的,javascript代码在运转前另有一个历程就是:预加载,预加载的目标是要事前组织运转环境比方全局环境,函数运转环境,还要组织作用域链(关于作用域链和环境,本文后续会做细致的解说),而环境和作用域的组织的核心内容就是指定好变量属于哪一个领域,因而在javascript言语里变量的定义是在预加载完成而非在运转时代。

所以,引子里的代码在函数的部分作用域下变量a被从新定义了,在预加载时刻a的作用域局限也就被框定了,a变量不再属于全局变量,而是属于函数作用域,只不过赋值操纵是在运转期实行(这就是为何javascript言语在运转时刻会转变变量的范例,由于赋值操纵是在运转期举行的),所以第一次运用a变量时刻,a变量在部分作用域里没有被赋值,只要栈区的标示称号,因而效果就是undefined了。

不过赋值操纵也不是完整不对预加载产生影响,预加载时刻javascript引擎会扫描一切代码,但不会运转它,当预加载扫描到了赋值操纵,然则赋值操纵的变量有无被var定义,那末该变量就会被给予全局变量即window对象。

依据上面的内容我们还能够明白下javascript两个迥殊的范例:undefined和null,从javascript变量存储的三部份角度思索,当变量的值为undefined时刻,那末该变量只要栈区的标示符,假如我们对undefined的变量举行赋值操纵,假如值是基础范例,那末栈区的值就有值了,假如栈区是对象那末堆区会有一个对象,而栈区的值则是堆区对象的地点,假如变量值是null的话,我们很天然以为这个变量是对象,而且是个空对象,依据我前面讲到的变量存储的三部份斟酌:当变量为null时刻,栈区的标示符和值都邑有值,堆区应当也有,只不过堆区是个空对象,这么说来null实在比undefined更耗内存了,那末我们看看下面的代码:

var ooo = null;

console.log(ooo);// 运转效果:null

console.log(ooo == undefined);// 运转效果:true

console.log(ooo == null);// 运转效果:true

console.log(ooo === undefined);// 运转效果:false

console.log(ooo === null);// 运转效果:true

运转之,效果很震动啊,null竟然能够和undefined相称,然则运用越发准确的三等号“===”,发明两者照样有点差别,实在javascript里undefined范例源自于null即null是undefined的父类,实质上null和undefined除了名字这个马甲差别,其他都是一样的,不过要让一个变量是null时刻必需运用等号“=”举行赋值了。

当变量为undefined和null时刻我们假如滥用它javascript言语能够就会报错,后续代码会没法一般运转,所以javascript开辟范例里请求变量定义时刻最好立时赋值,赋值优点就是我们背面不论如何运用该变量,递次都很难由于变量未定义而报错从而停止递次的运转,比方上文里就算变量是string基础范例,在变量定义属性递次照样不会报错,这是提拔递次健壮性的一个主要手腕,由引子的例子我们还晓得,变量定义最好放在变量所述作用域的最前端,这么做也是保证代码健壮性的一个主要手腕。

下面我们再看一段代码:

var str;

if (undefined != str && null != str && "" != str){

    console.log("true");

}else{

    console.log("false");

}

if (undefined != str && "" != str){

    console.log("true");

}else{

    console.log("false");

}

if (null != str && "" != str){

    console.log("true");

}else{

    console.log("false");

}

if (!!str){

    console.log("true");

}else{

    console.log("false");

}

str = "";

if (!!str){

    console.log("true");

}else{

    console.log("false");

}

运转之,效果都是打印出false。

运用双等号“==”,undefined和null是一回事,所以第一个if语句的写法完整过剩,增加了不少代码量,而第二种和第三种写法是等价,究其实质前三种写法实质都是一致的,然则实际中许多递次员会选用写法一,启事就是他们还没明白undefined和null的差别,第四种写法是越发圆满的写法,在javascript里假如if语句的前提是undefined和null,那末if推断的效果就是false,运用!运算符if盘算效果就是true了,再加一个就是false,所以这里我发起在誊写javascript代码时刻推断代码是不是为未定义和null时刻最好运用!运算符。

代码四里我们看到当字符串被赋值了,然则赋值是个空字符串时刻,if的前提推断也是false,javascript里有五种基础范例,undefined、null、boolean、Number和string,如今我们发明除了Number都能够运用!来推断if的ture和false,那末基础范例Number呢?

var num = 0;

if (!!num){

    console.log("true");

}else{

    console.log("false");

}

运转之,效果是false。

假如我们把num改成负数或正数,那末运转之的效果就是true了。

这申明了一个原理:我们定义变量初始化值的时刻,假如基础范例是string,我们赋值空字符串,假如基础范例是number我们赋值为0,如许运用if语句我们就能够推断该变量是不是是被运用过了。

然则当变量是对象时刻,效果却不一样了,以下代码:

var obj = {};

if (!!obj){

    console.log("true");

}else{

    console.log("false");

}

运转之,代码是true。

所以在定义对象变量时刻,初始化时刻我们要给变量给予null,如许if语句就能够推断变量是不是初始化过。

实在if加上!运算推断对象的征象另有玄机,这个玄机要等我把场景三讲完才说清楚哦。

场景三:复制变量的值和函数通报参数

起首看看这个场景的代码:

var s1 = "sharpxiajun";

var s2 = s1;

console.log(s1);//// 运转效果:sharpxiajun

console.log(s2);//// 运转效果:sharpxiajun

s2 = "xtq";

console.log(s1);//// 运转效果:sharpxiajun

console.log(s2);//// 运转效果:xtq

上面是基础范例变量的赋值,我们再看看下面的代码:

var obj1 = new Object();

obj1.name = "obj1 name";

console.log(obj1.name);// 运转效果:obj1 name

var obj2 = obj1;

console.log(obj2.name);// 运转效果:obj1 name

obj1.name = "sharpxiajun";

console.log(obj2.name);// 运转效果:sharpxiajun

我们发明当复制的是对象,那末obj1和obj2两个对象被串连起来了,obj1变量里的属性被转变时刻,obj2的属性也被修正。

函数通报参数的实质就是外部的变量复制到函数参数的变量里,我们看看下面的代码:

function testFtn(sNm,pObj){

    console.log(sNm);// 运转效果:new Name

    console.log(pObj.oName);// 运转效果:new obj

    sNm = "change name";

    pObj.oName = "change obj";

}

var sNm = "new Name";

var pObj = {oName:"new obj"};

testFtn(sNm,pObj);

console.log(sNm);// 运转效果:new Name

console.log(pObj.oName);// 运转效果:change obj

这个效果和变量赋值的效果是一致的。

在javascript里通报参数是按值通报的。

上面函数传参的题目是许多公司都爱口试的题目,实在许多人都不晓得javascript传参的实质是如何的,假如把上面传参的例子改的复杂点,许多朋侪都邑栽倒到这个口试题下。

为了申明这个题目标原理,就得把上面讲到的变量存储原理综合运用了,这里我把前文的内容再复述一遍,两张图,以下所示:

《谈谈javascript语法里一些难点题目(一)》

这是基础范例存储的内存组织。

《谈谈javascript语法里一些难点题目(一)》

这是援用范例存储的内存组织。

另有个学问,以下:

在javascript里变量的存储包括三个部份:

  • 部份一:栈区的变量标示符;

  • 部份二:栈区变量的值;

  • 部份三:堆区存储的对象。

在javascript里变量的复制(函数传参也是变量赋值)实质是传值,这个值就是栈区的值,而基础范例的内容是存放在栈区的值里,所以复制基础变量后,两个变量是自力的互不影响,然则当复制的是援用范例时刻,复制操纵照样复制栈区的值,然则这个时刻值是堆区对象的地点,由于javascript言语是不允许操纵堆内存,因而堆内存的变量并没有被复制,所以复制援用对象复制的值就是堆内存的地点,而复制两边的两个变量运用的对象是雷同的,因而复制的变量个中一个修正了对象,另一个变量也会受到影响。

原理讲完了,下面我枚举一个拔高的例子,代码以下:

var ftn1 = function(){

    console.log("test:ftn1");

};

var ftn2 = function(){

    console.log("test:ftn2");

};

function ftn(f){

   f();

   f = ftn2;

}

ftn(ftn1);// 运转效果:test:ftn1

console.log("====================华美的分割线======================");

ftn1();// 运转效果:test:ftn1

这个代码是很早之前有位朋侪考我的,我当时答对了,然则我是蒙的,问我的朋侪答错了,实在当时我们两个都没搞懂个中启事,我朋侪是这么剖析的他以为f是函数的参数,属于函数的部分作用域,因而变动f的值,是没法转变ftn1的值,由于到了外部作用域f就失效了,然则这类诠释很难申明我上文里给出的函数传参的实例,实在这个题目答案就是函数传参的原理,只不过这里加入了个殽杂要素函数,在javascript函数也是对象,部分作用域里f = ftn2操纵是将f在栈区的地点改成了ftn2的地点,对外部的ftn1和ftn2没有任何转变。

记着:javascript里变量复制和函数传参都是在通报栈区的值。

栈区的值除了变量复制起作用,它在if语句里也会起到作用,当栈区的值为undefined、null、“”(空字符串)、0、false时刻,if的前提推断则是为false,我们能够经由过程!运算符盘算,因而当我们的代码以下:

var obj = {};

if (!!obj){

    console.log("true");

}else{

    console.log("false");

}

效果则是true,由于var obj = {}相当于var obj = new Object(),虽然对象里没什么内容,然则在堆区里,对象的内存已分配了,而变量栈区的值已是内存地点了,所以if语句推断就是true了。

看来本主题又没法写完,实在原本我写本文是想讲new,prototype,call(apply)以及this,没想讲变量定义就讲了这么多,算了,先发表出来吧,吃了晚餐接着写,愿望本日写完。

原文出处:谈谈javascript语法里一些难点题目(一)

    原文作者:Pocher
    原文地址: https://segmentfault.com/a/1190000002593906
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞