从新认识JavaScript(一)

上一篇:从新认识JavaScript(零)前端

上一篇文章中,介绍了JavaScript的一些概念,以及在HTML页面中如何使用JavaScript。这一篇,开始了解JavaScript的基础知识。面试

什么是变量

变量,就是用于存放数值的容器。浏览器

这个数值能够是一个能够用于累加计算的数字,或者是一个句子中的字符串。变量的独特之处在于它存放的数值是能够改变的。先来看一个简单的例子:闭包

<button>点我</button>
复制代码
// querySelector返回文档中与指定选择器或选择器组匹配的第一个HTML元素
const button = document.querySelector('button');

button.onclick = function () {
    /* prompt方法用于显示可提示用户进行输入的对话框,返回用户输入的字符串 取消返回null */
    let name = prompt('你叫什么名字?');
    alert(`欢迎你,${name}!`);
};
复制代码

在这个例子里面,第一行代码会弹出一个输入框,让用户输入一个名字,而后将这个名字存在一个变量中。第二行代码会弹出一个带有刚才输入名字的信息。变量name则存储了刚才用户输入的名字。函数

变量的另外一个特性是能够存储任何东西,不止是数字和字符串。变量能够存储更复杂的数据,甚至是函数。post

这里仍有一个重要的概念须要区分。变量不是数值自己,它仅仅是一个用于存储数值的容器。ui

声明变量

想要使用一个变量,那么第一件事固然是建立它。更专业的说法是声明一个变量。声明一个变量的方法是在varlet关键字后面加上这个变量的名字:this

var myName;
let myAge;
复制代码

提示:在JavaScript中,全部代码指令都会以;结尾。若是忘记加分号,你的单行代码可能执行正常,可是多行代码在一块儿可能会执行出错。因此最好养成主动以分号区分代码结尾的习惯。spa

能够经过使用变量的方式来验证这个变量的数值是否在执行环境中已经存在。例如:设计

myName;
myAge;
复制代码

以上这两个变量并无数值,它们是空的容器。当使用这两个变量时,会获得一个undefined的返回值。若是使用一个不存在的变量,则会获得一个报错信息。

提示:“一个变量存在,但它没有数值”和“一个变量不存在”彻底是两回事。不存在乎味着没有存放数值的容器,另外一个则意味着这是一个空的容器。

初始化变量

一旦声明了一个变量,就可使用它。方法是在变量名后面跟上一个=,再在后面加上数值:

myName = '伊丽莎不白';
myAge = 32;
复制代码

如今再使用这两个变量,会发现它们已经存储了刚刚赋于它们的数值。一样,也能够在声明变量的时候初始化变量:

let myName = '伊丽莎不白';
复制代码

var 与 let 的区别

这是JavaScript的一个历史遗留问题,一样也是绝大多数前端开发面试必问的一个问题。值得认真对待。

最初在建立JavaScript时,是只有var的。在大多数状况下,这种方法能够接受,但有时在工做方式上会有一些问题:它的设计会使人困惑。所以let是现代版本JavaScript建立的一个新关键字,用于建立与var工做方式有些不一样的变量,解决了过程当中的问题。

下面解释这些差别。

定义

首先来看varlet的定义。

  • var声明语句声明一个变量,并可选的将其初始化为一个值。
  • let语句声明一个块级做用域的本地变量,而且可选的将其初始化为一个值。

描述

var变量声明,不管发生在什么地方,都在执行任何代码以前处理。用var声明的变量的做用域是它当前的执行上下文,能够是嵌套的函数,也能够是声明在任何函数外的变量。若是从新声明一个变量,这个变量不会丢失它的值。

变量提高

因为变量声明老是在任意代码执行以前处理的,因此在代码中任意位置声明变量老是等效于在代码开头声明。这意味着能够在变量声明以前使用变量:

myName = '伊丽莎不白';
var myName;

/* 能够理解为 var myName; myName = '伊丽莎不白'; */
复制代码

所以,建议始终在做用域顶部声明变量(全局代码的顶部和函数代码的顶部),这能够清楚的知道哪些变量是函数做用域,哪些变量在做用域链上解决。

重要的是,提高仅影响变量声明,而不会影响其数值的初始化:

var a = 'A';
// => A
var b = a;
// => A
复制代码
var b = a;
// => undefined
var a = 'A';
// => A
复制代码

做用域限制

声明变量的做用域限制在其声明位置的上下文中:

function init () {
    var a = 1;
}

init();

console.log(a);
// => ReferenceError,变量a做用域在init函数内部,未在init函数外部声明
复制代码

重复声明

能够在代码中根据须要屡次声明名称相同的变量:

var myName = '伊丽莎不白';
var myName = '不白';
// => 不白
复制代码

let容许声明一个做用域被限制在块级中的变量、语句或表达式。与var不一样的是,let声明的变量只能是全局或者整个函数块的。

做用域规则

let声明的变量只在其声明的块或子块中可用,这一点与var类似。两者最主要的区别在于var声明的变量的做用域是整个封闭函数。

function varTest () {
    var a = 1;
    if (true) {
        // 变量提高,a为相同的变量
        var a = 2;
        console.log(a);
        // => 2
    }
    console.log(a);
    // => 2
}

function letTest () {
    let a = 1;
    if (true) {
        // a为不一样的变量
        let a = 2;
        console.log(a);
        // => 2
    }
    console.log(a);
    // => 1
}
复制代码

简化内部函数代码

当用到内部函数时,let会让代码变得更简单。下面用一段很是常见的面试代码来举例:

for (var i = 0; i < 5; i++) {
    var btn = document.createElement('button');
    btn.innerHTML = `按钮${i}`;
    
    var j = i;
    btn.onclick = function (evt) {
        console.log(`点击了按钮${j}`);
    };
}
复制代码

上面这段代码的意图是建立5个按钮,点击不一样的按钮可以打印出当前按钮的序号。然而结果将老是打印出点击了按钮5。由于j是函数级变量,5个内部函数都指向了同一个j,而j最后一次的赋值是5。

若是想要实现预期的效果,只须要将j的声明语句改为let。这样j变成了块级域,因此5个内部函数指向了不一样的j。

在程序或函数的顶层,let不会像var同样在全局对象上建立一个变量,例如:

var a = 1;
let b = 2;
console.log(this.a);
// => 1
console.log(this.b);
// => undefined
复制代码

模仿私有接口

在处理构造函数的时候,能够经过let声明而不是闭包来建立私有接口。

{
    let _age = 32;
    
    var Person = function () {
        this.name = '伊丽莎不白';
    };
    
    Person.property.growUp = function () {
        _age++;
        console.log(_age);
    };
}

var p = new Person();
p.growUp();
// => 33
console.log(_name);
// => ReferenceError
复制代码

这里须要注意一点,由于_name是被Person的实例所持有的,因此在使用中会存在内存泄漏的风险。闭包也是同理。

重复声明

在同一个函数或块做用域中重复声明一个变量会引发SyntaxError。

let myName;
let myName;
// => SyntaxError
复制代码

switch语句中只有一个块做用域,可能会所以引起错误:

let a = 1;
switch (a) {
    case 0:
        let b;
        break;
    case 1:
        let b;
        // => SyntaxError
        break;
}
复制代码

然而,一个嵌套在case字句中的块会建立一个新的块做用域,不会产生上面提到的错误:

let a = 1;
switch (a) {
    case 0: {
        let b;
        break;
    }
    case 1: {
        let b;
        // => SyntaxError
        break;
    }
}
复制代码

暂存死区

let被建立在包含该声明的做用域的顶部,称之为“提高”。与var不一样,经过let声明的变量直到它们的定义被执行时才初始化。该变量处在一个自块顶部到初始化处理的“暂存死区”中,这之间对变量的访问会形成错误:

console.log(a);
// => undefined
console.log(b);
// => ReferenceError
var a = 1;
let b = 2;
复制代码

let后面跟一个函数传递的参数时将致使循环内部报错:

function go (n) {
    for (let n of n.a) {
        console.log(n);
    }
}

go({ a: [1, 2, 3] });
// => ReferenceError
复制代码

更新变量

一旦变量赋值,还能够经过简单的给它一个不一样的值来更新它。

let myAge = 32;
myAge = 18;
// => 18
复制代码

变量类型

能够为变量设置不一样的数据类型,例如:

let myName = '伊丽莎不白';
// => String
let myAge = 32;
// => Number
let isAlive = true;
// => Boolean
let myTels = [10086, 10010];
// => Array
let myCat = {
    name: 'Pie',
    age: 4
}
// => Object
复制代码

动态类型

与C或Java不一样,JavaScript是一种“动态类型语言”,这意味着在使用过程当中不须要为变量指定一个数据类型。

声明一个变量并赋给它一个带引号的值,那么浏览器将会知道它是一个字符串:

let myName = '伊丽莎不白';
复制代码

即时包含数字,依然是一个字符串:

let myAge = '32';
// => String
myAge = 32;
// => Number
复制代码

总结

到如今为止,对变量已经有了一个较为全面的认识,尤为是对varlet的使用。下一篇文章将继续深刻JavaScript,介绍数字与字符串。

下一篇:从新认识JavaScript(二)

相关文章
相关标签/搜索