本篇主要涉及较为基础的, 变量, 作用域方面的名词。
为啥要知道这些名词的确实含义?
提升自学效率
应付考试
装逼
比如 js权威指南一书中多次提到“直接量” 还有“对象的直接量用法”之类的,如果每次都度娘,浪费时间不说,老是看书卡住然后再度娘,还徒增挫败感,说不定还记不住。所以就需要我们能够提前了解一些名词,为自学相关领域铺平道路。毕竟,学过基本js的行话(概念),看js红宝书,乃至vue react有关的书籍就不容易卡壳了)
直接量(literal)
程序中直接使用的数据值,没啥了不起的。
有时又称为“字面量”,如对象字面量。
下面列举一些直接量:
直接量 | 实例 |
---|---|
数字 | 1.2 |
字符串 | “Hello world” |
布尔 | true |
正则表达式 | /javascript/gi |
数组 | [1,2,3,4] |
对象 | {id:1, title:} |
未定义的变量 | undefine |
不存在的对象 | NULL |
广义上undefine和null也算,至于为什么请看下一节。
当然了这些都无关紧要。
Undefine NULL
如果一个值定义了,却没有具体值,类似于NaN,那就称为Undefine。
类似的,我们如果想寻找一个对象,可是它确实“找不到的”,比如可能是,所寻找的对象根本不存在,或者不在同一个域,那返回的结果就是NULL(类似“查无此人”)。
typeof(null) // 结果是‘Object’
当然实际中运用还要进一步判断是null(找不到的对象)还是普通的对象直接量
其实根本区别就是你原本想寻找的种类(变量 值还是对象)不同,JS反馈给你“找不到”,这个结果的表达方式的不同。其他深层次还有别的问题,但我们其实碰不到太多。
包装对象
//测试环境 Chrome F12 console
var s1 = "Hello Vue";
var s2 = s1.substring(s1.indexOf(" ")+1, s1.length);
s1; //"Hello Vue"
s2; //"Vue"
本章第一节说,字符串属于直接量,程序直接使用,意味着人家并不是对象,那咋会有方法呢?
什么东西才有方法?
他是个对象
他有属性
他的其中一个属性是个函数
那个函数称为对象的方法
不单单字符串,数字布尔值等直接量都会有这类现象。(当然,上一节的分析告诉我们,这个现象不包含undefined和NULL)
这就是js灵活的地方
JS:“你想要用方法处理这些直接量是吧?让字符串小写变大写,反序啥的,再得到结果?,好的,我帮你暗箱操作一下就行了”。
JS:“反正结果正确就行了”
JS:“什么?只有对象能用?那怕啥,我神不知鬼不觉的生成一个临时对象就可以用了嘛”
(所谓临时对象就是 临时的包装对象)
JS:“到时候用完了我再 销毁证据就行了”
(就是把临时的包装对象销毁 至于具体实现时是啥时候销毁 未可知也 但是,你暗箱操作完就不能用了)
JS:“啥?你怕证据会被查到?来来来,我们看看我上次的杰作:”
var string_1 = "test";
string_1.len = 4; //我“留下的操作痕迹”
var What_I_Want = string_1.reverse();//我想要的信息已经转移到我账户了
var attribution_1 = string_1.len; //政府审计在查询属性......
JS:“他绝B审查不出来!(attributon_1 === undefined) == True”
这里,为啥改不了呢?
你更改的不过是他的包装对象。
所以直接对直接量操作方法,其实只能获取信息而并不能改变,这也是js一个规矩——数值 布尔 字符串不能直接改变
前面提到“临时的包装对象”,那自然有真正的,持久的包装对象,就是我们直接,显式装换得到的:
var str_1 = "test", num_1 = 1, bool_1 = true;
var strObject = new String(string_1);
var numObject = new Number(num_1);
var bollObject = new Boolean(bool_1);
这些String() Number() Boolean()将常量转为包装对象
JS通人性的地方不仅仅是说,机智的知道你想像操作对象一样操作常量,比如,他还知道转换后的包装对象和之前的常量大致是一个东西。当然,你硬要区分,就用‘===’就ok了。
strObject == str_1;
//true
strObject === str_1;
//false
变量作用域
域就是区域,一块地儿。
假设变量们都成家立业了,互相之间走亲戚需要知道地址,那么全局变量是最骚的——谁都知道他的地址(全局作用域,在代码中任何地方都有定义,都能访问)。
局部变量比较保守,“我只在自己的圈子里,自己人才知道我的地址”(局部作用域)。
不扯了2333.
我们知道变量声明推荐用的是var str_1, 不用会咋样?不好意思,有时候JS不知道你是声明+初始赋值,还是改变已有变量的值:
var global_varible = "我是国际老鼠";
function checkScope(){
scope = "我是本地老鼠";
return scope;
}
checkScope() // => "我是本地老鼠"
为啥国际老鼠(全局变量)变成本地老鼠了(局部变量)?
JS:“我鬼知道scope = ‘我是本地老鼠’ 这句是改变全局的那个scope还是局部的scope?”
所以,为了您的身心健康,声明变量用var, 除非你不是在声明变量2333。
这里有个嵌套(nested)demo:
var mouse = "International mouse";
function ChinaMice(){
var mouse = "Chinese mouse";
function BeiJingMice(){
var mouse = "BeiJing mouse";
return mouse;
}
return BeiJingMice();
}
var Who = mouse; //还是国际老鼠
ChinaMice() //北京老鼠还是中国老鼠还是国际老鼠?
函数作用域 块级作用域
函数作用域(function scope) 就是JS采用的作用域:我函数无论声明在代码的最后还是最前,整个作用域都能访问到我。这个很人性化,
但是,“C语言不是这样的吧?”
“C语言必须把函数声明放最前面,后面的弟弟们才能访问到它。”
对,所以c语言就不是函数作用域。
而JS就被非正式的称为有“变量提前”的特性。
但私以为 最好为了反映真实的变量作用域,将变量放最前面。
想像一下,
var mouse = "国际老鼠";
function(){
console.log(mouse);
/*...
...
...
...
... n行代码 ...
*/
var mouse = "本地老鼠";
}
其实你本来想用一下全局变量国际老鼠的,你的猪队友在你后面没事又声明了一下,你就只能获得 undefined了!注意,连本地老鼠都不是哟。所以团队协作就统一口径在 作用域最前面来声明变量。
为什么不是“本地老鼠”?变量声明的确是提前,然并暖——赋值还是老地方,因此,声明和赋值地方不在一起,而且声明不放最前面,你看你队友不打死你:)
块级作用域(block scope),这个在ES6终于支持了,明显,因为我们函数作用域,导致一系列的弊端,之前说的局部未赋值变量随便就覆盖了全局变量,还有局部变量泄漏到全局作用域去,污染了全局作用域,如下栗子:
var str_1 = "mouse";
for (var i = 0;i < str_1.length; i++){
console.log(str_1[i]);
}
console.log(i); //5
那么我们用let 也就是ES6出来的,支持块级作用域的方式,看看效果:
var str_1 = "mouse";
for (let i = 0;i < str_1.length; i++){
console.log(str_1[i]);
}
console.log(i); //undefined
还有一种语句块(block)的形式:
{
let t= 1;
t = t * t +1;
}
console.log(t); // undefined
是block结合let的效果,非常人性化。
作用域链 变量解析 引用错误
可以想象,父作用域包着子作用域,子作用域包着孙作用域…这就有点类似一个链条,大环扣小环。那么抽象这个链条有啥子用呢?
答案昭然若揭:
装逼
其实是为了变量解析(varible resolution)
我们JS工作的时候,遇到一变量,比如X,就要去找他的值(解析他,也就是想知道:“X是个嘛玩意儿”),如果孙子作用域没有,就在儿子作用域找,儿子没有找老爸,老爸没有找爷爷, etc, 找呀找呀找朋友找变量的过程称为变量解析(varible resolution),而找的方法就是沿着作用域链。
找到祖宗都找不到呢?这是就会报错:引用错误(ReferenceError)。
[1]: 《Javascript 权威指南(第六版)》.(美)David.Flanagan.
[2]: 《JavaScript高级程序设计(第三版)》.(美)Nicholas C.Zakas.
[3]: 《Javascript语言精粹》.(美)Douglas Crockford