JavaScript 基础之变量声明

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战javascript

前言

了解 JavaScript 的人比比皆是,理解 JavaScript 的人寥寥无几。java

本文将带着你们一块儿深刻了解 JavaScript 基础之变量声明,废话少说,那就开始吧。es6

目录

能够看下下面的思惟导图编程

变量声明有哪些?

变量声明的方式有:浏览器

  • var:声明变量
  • let:声明变量
  • const:声明常量
  • function:声明一个函数
  • import:导出一个模块
  • class:声明一个类

var 声明

为何会出现 var 声明?

咱们先来看看不使用 var 声明,可能会出现什么问题?安全

在控制台输入如下代码markdown

myName = '追梦玩家';

console.log(window.myName); // 请问,这个结果是?

function fn() {
    myName = '追梦玩家1';
}
fn();
console.log(window.myName); // 请问,这个结果是?
复制代码

结果,能够看下截图app

有个疑问,为何我在 fn 函数里面给 myName 进行赋值 "追梦玩家1",执行完 fn 函数,打印 window.myName,打印出的结果是“追梦玩家1”?编程语言

其实这就是涉及到一个概念,就是不使用 var 声明的变量,是全局变量,全局变量其实就是在 window 对象中,添加属性并赋值。函数

最重要的一点,就是若是在函数里面,执行 myName = '追梦玩家1'; 这个代码,至关于从新设置下 window.myName,这样的话,有可能覆盖以前声明的变量值。

// myName = '追梦玩家1'; // 至关于直接给 window 添加属性并赋值
window.myName = '追梦玩家1';
复制代码

var 声明变量的优缺点

相信你们平时声明变量,都是使用 let 或者 const 声明,那为何还要说下 var 声明变量的优缺点呢?

之前使用 var 声明变量,多是有它存在的意义。而新特性的出现,每每都是为了解决某些问题而出现。

若是只知其然而不知其因此然,只是去背用法,但不知道为何须要这样用,不只不能融会贯通,并且也没有办法促使本身进步。

所以在下面介绍 let 和 const 以前,咱们要先了解下使用 var 声明有什么优缺点,才能理解为何会有 let 和 const 的出现。

优势

解决函数执行,会覆盖以前声明的变量

我以为使用 var 声明变量,仍是有优势,只不过很少。好比,上面在函数里面,不使用 var 声明的变量,会覆盖以前声明的变量值。

使用 var 声明,能够解决“函数执行,覆盖以前的声明变量”的问题

function fn() {
    var myName = '追梦玩家';
}
fn();
console.log(window.myName); // 请问,这个结果是?
复制代码

相信你们动手尝试下,均可以知道,这个结果是 "",而不是 “追梦玩家”。可是在全局做用域下,使用 var 声明变量,仍是至关于给 window 添加属性,那有办法解决吗?请看下面使用 var 声明的缺点。

缺点

在全局做用域下,var 声明会给 window 设置属性

在全局做用域下声明一个变量,也至关于给 window 全局对象设置了一个属性,变量的值就是属性值(私有做用域中的声明的私有变量和 window 没啥关系)简单来讲,全局变量和 window 中的属性存在“映射机制” 一个被修改,另一个也跟着变 。

在控制台输入如下代码,看下输出结果

// 说明会给 window 设置属性
var myName = '追梦玩家';
console.log(window.myName); // 请问,这个结果是?

// 说明:全局变量和 window 的属性,会存在“映射机制”
window.myName = '砖家';
console.log(myName); // 请问,这个结果是?
复制代码

若是你们理解“映射机制”的话,就很好懂了,就是给 myName 或者 window.myName 赋值,window.myName 和 myName 都会发生改变。

结果,能够看下截图

有办法解决这个问题?有的,能够经过当即执行函数来实现

当即执行函数的做用,就是建立一个独立的做用域,这个做用域里面的变量,外面访问不到,即避免「变量污染」。

// 当即执行函数,其实就是声明一个函数,而后立刻执行
!function() {
    var myName = '追梦玩家';
}();
console.log(window.myName); // 输出结果?
console.log(myName); // 输出结果?
复制代码
存在变量提高

下面经过一个例子,来理解

console.log(myName); // 输出结果?是否会报错?
var myName = '追梦玩家';
console.log(myName); // 输出结果?
复制代码

相信你们,在学习 JavaScript 基础的时候,都有遇到过相似的代码。为何在声明 myName 变量以前,打印 myName,不会报错,输出结果是 undefined,这就是涉及到 JavaScript 一个概念:变量提高。

注:使用 function 声明的函数,也会有变量提高。

想了解“变量提高”,可看这篇文章:我知道你懂 hoisting,但是你了解到多深?

容许重复声明

即便在同一个做用域,一样名称的变量,也容许重复声明,不会出现任何错误或者警告,很容易忽略本身有声明过这个变量。

值得注意的是,重复声明一个变量,并不会从新设置这个变量的值。

举个例子,好比一段很长的代码,咱们可能不记得前面有声明过变量,后面再次使用 var 声明,当作第一次声明,容易形成小 Bug。

var myName = '追梦玩家';
// 写了不少代码以后
// ...
// ...
// ...
var myName;
while(true){
    if( name === undefined ){
         console.log('The first time to execute.'); // 这里会被执行吗?
    }
    // ...
}
复制代码
没有块级做用域

先看一下这个例子:

function fn() {
  {
  	var myName = '追梦玩家';
  }
  console.log('fn(): myName=', myName);
}
fn();
复制代码

执行结果:

fn(): myName= 追梦玩家
复制代码

使用 var 声明的变量并不具有块级做用域的效果。

而块级做用域特性在其余编程语言很常见,es6 出现的块级做用域,其实就是参考其余编程语言,借鉴优秀经验。

不支持声明常量

常量指的是「固定不变的值,没法从新修改的」。

在程序里面,有时候,有些值是只须要声明一次,不须要也不但愿在程序执行的过程当中被修改。

例如,数学的 PI 是 3.14,若是是程序中某个地方修改了这个值,就会致使结果出现错误。

var PI = 3.14;
PI = 1024;
复制代码

可是使用 var 声明,没有办法作到这一点,你想怎么改变值都是能够,程序代码执行,会存在不肯定性,有风险。

许多其余编程语言,对于常量,都有提供管控机制来提升安全性,一旦误该,可能编译阶段就能发现,甚至在 IDE 在编写代码阶段就能提醒,减小 debug 负担。

let 声明

为何会出现 let 声明?

let 跟 var 的做用差很少,但有着很是重要的区别。最明显的区别是,let 声明的范围是块级做用域,而 var 声明的范围是函数做用域。let 是在 ES6 版本出现的,其实就是为了解决使用 var 声明变量,遇到的问题,或者说是缺点。具体能够继续往下看。

let 解决了什么问题?

全局声明,不会成为 window 对象的属性

能够看下下面的例子:

var myName = '追梦玩家';
console.log(window.myName); // 输出结果?

let age = 18;
console.log(window.age); // 输出结果?
window.age = 30;
console.log(age); // 输出结果?
复制代码

具体结果,请看截图

说明一下,上面代码为何给 window.age 进行赋值呢?由于使用 var 声明的话,会跟 window 的属性存在“映射机制”,因此要这样去测试下。

修改了 window.age,打印 age,仍是 18,因此得出结论:使用 let 在全局做用域中声明的变量不会成为 window 对象的属性。

没有变量提高

看下下面的例子:

console.log(myName); // 输出结果?
let myName = '追梦玩家';
复制代码

若是是使用 var 声明的话,输出的结果确定是 undefined,可是使用 let 声明的话,是输出 undefined,仍是报错?

结果固然是报错啦。由于 let 是没有变量提高,在声明以前访问 myName 变量的话,就出现报错:

不容许重复声明

看下下面的例子:

let myName = '追梦玩家';
// ... 写了好多代码
let myName;
复制代码

在控制台,执行上面的代码,会出现报错,浏览器说,myName 已经被声明过了,你为何还要声明,给个语法报错,本身看看吧。

咱们回忆下,若是是使用 var 声明的变量,是否会出现报错呢?打印 myName 变量的值是?

不会有报错,myName 是追梦玩家。

存在块级做用域

首先先了解下,什么是块级做用域?

块级做用域就是使用一对大括号包裹的一段代码,好比函数、判断语句、循环语句,甚至单独的一个{}均可以被看做是一个块级做用域。

//if块
if(1){}

//while块
while(1){}

//函数块
function foo(){
 
//for循环块
for(let i = 0; i<100; i++){}

//单独一个块
{}
复制代码

简单来讲,块级做用域里面的定义的变量,在代码块外部是访问不到的。

理解上面这句话,就知道执行下面的代码,其实会报错:Uncaught ReferenceError: myName is not defined

{
    let myName = '追梦玩家';
}
console.log(myName); // 输出结果?
复制代码

特性

暂时性死区

咱们先来了解下,暂时性死区的做用:

ES6 规定暂时性死区和 let 、const 语句不出现变量提高,主要是为了减小运行时的错误,防止在变量声明前就是用这个变量,从而致使意料以外的行为。

暂时性死区:只要块级做用域内存在 let、const 命令,它所声明的变量就“绑定”(binding)这个区域,再也不受外部的影响。

结合下面的例子来理解下:

// var num = 1;
if (true) {
  num = 'a'; // Error: Uncaught ReferenceError: Cannot access 'num' before initialization
  let num;
}
复制代码

由于在 if 中声明了一个局部变量,致使出现暂时性死区,if 里面的 num 则与这个块级做用域捆绑在一块儿,不在受全局变量 num 的影响,同时 let 不存在变量提高,因此在 let 前赋值的 num 是非法的。那个报错,就好像是浏览器说:不要在声明以前使用 num。const 与之同理

其本质就是,只要进入当前做用域,所要使用的变量就已经存在,可是不可获取,只有等到声明变量的那一行代码出现,才能够获取和使用该变量。

条件声明

由于 let 的做用域是块,因此不可能检查前面是否已经使用 let 声明过同名变量,同时也就不可能在没有声明的状况下声明它。

看下下面这个例子:

if (typeof name === 'undefined') { 
    let name; 
}
// name 被限制在 if {} 块的做用域里面
name = '追梦玩家'; 
// 执行上面这行代码,至关因而给 window 添加一个属性 name,进行赋值,而不是给变量 name 赋值,
// 由于变量 name 被限制在代码块的做用域里面
复制代码

const 声明

const 的行为与 let 基本相同,惟一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会致使运行时错误。

上面这个区别,要怎么理解呢?

能够看下这个例子:

// 若是使用 const 声明,后面进行修改,会出现报错
const myName = '追梦玩家';
myName = '砖家'; 
复制代码

这个报错,就好像是浏览器说:不能够给常量赋值,都说了是常量,懂不。

你们有可能看到过相似的代码

const person = { name: '追梦玩家' };
person.name = '砖家';
person.age = 18;
console.log(person); // 输出结果?
复制代码

并无出现报错,而是输出一个对象。

这其实就是涉及到一个问题:const 定义的对象属性是否能够改变

person 对象的 name 属性确实被修改了,也能够给 person 对象添加属性,怎么理解这个现象呢?

由于对象是引用类型,person 中保存的仅仅是内存地址,也就是 const 声明的限制只适用于它指向的变量的引用。

简单来讲,怎么理解内存地址呢?

内存地址,就比如就是仓库的钥匙,由于对象,是能够往里面添加、修改属性,就比如如,是一个大仓库。你有仓库的钥匙,就能够往里面放东西。

换句话说,使用 const 定义的对象,是能够修改这个对象内部的属性的。

function

为何会出现函数?

function 关键字,建立函数,其实函数名也是变量,只不过存储的值是引用数据类型而已。

在JS中,函数就是一个方法(一个功能体),基于函数通常都是为了实现某个功能「好比洗衣机就是一个函数」,为何说洗衣机是一个函数呢?

其实,洗衣机提供入口,给你放衣服和洗衣粉以后,洗衣机将衣服洗干净以后,给你的是干净的衣服。提供入口,就至关因而函数的参数,给你干净的衣服,就至关于函数处理完某些操做,返回一些东西给你。

var total = 10;
total += 10;
total = total /2;
total = total.toFixed(2);

/* 上面这个代码,就是想作下简单的数学运算,若是在后续的代码中, 咱们依然想实现相同的操做(加10除以2),咱们是要复制代码,粘贴代码, 这样的话,就会致使页面中存在大量冗余的代码,也下降了开发效率。 若是咱们能把实现这个功能的代码进行“封装”,后期须要这个功能,调用方法便可,方法也就是函数。*/
复制代码

函数诞生的目的就是为了实现封装:把实现一个功能的代码封装到一个函数中,后期想要实现这个功能,只须要把函数执行便可,没必要要再重复编写重复的代码,起到了低耦合高内聚(减小页面中的冗余代码,提升代码的重复使用率)的做用。

function fn(){
	var total = 10;
    total += 10;
    total /= 2;
    total = total.toFixed(2);
    console.log(tatal);
}
fn();
fn();
...
// 想用多少次,咱们就执行多少次函数便可
复制代码

建立函数

ES3 标准

/* // => 建立函数 function 函数名([参数]){ 函数体:实现功能的JS代码 } // => 把函数执行 函数名(); */

// 建立函数
function fn() {
	console.log('hi');
}
fn(); // 函数执行
复制代码

ES6 标准中建立箭头函数

/* let 函数名(变量名) = ([参数])=>{ 函数体 } 函数名(); */

let fn = () =>{
    let total = 10;
    // ...
}
fn();
复制代码

函数的形参和实参

参数是函数的入口:当咱们在函数中封装一个功能,发现一些原材料不肯定,须要执行函数的时候用户传递进来才能够,此时咱们就基于参数的机制,提供出入口。「好比是生产洗衣机的厂家,并不知道用户是怎么操做,他们只须要提供一些孔,入水口」

//=>此处的参数叫作形参:入口,形参是变量(n / m 就是变量)
function sum(n,m){
    //=> n 和 m 分别对应 要求和的两个数字
    var total = 0;
    total = n + m;
    console.log(total);
}

// => 此处函数执行传递的值是实参:实参是具体的数据值
sum(10,20);  // => n=10 m=20
sum(10); //=> n=10 m=undefined
sum(); //=> n 和 m 都是undefined
sum(10,20,30);  // => n=10 m=20 30没有形参变量接收
复制代码

简单来讲,函数调用的时候,传递的是实参。建立的函数的时候,圆括号里面的参数,是形参。

函数的 return

咱们先来看看这个需求:在函数外面获取到 total 的值,要怎么获取呢?这就要用到 return 啦。

function fn(n,m) { 
    var total = 0; // => total:私有变量
    total = n + m;
    return total; // => 并非把 total 变量返回,返回的是变量存储的值,return 返回的永远是一个值
}

// 下面先说一下 fn 和 fn() 的区别
fn // => 表明的是函数自己 只建立函数,不去使用,并无意义
fn(10,20); // => 表明的是函数执行(不只如此,它表明的是函数执行后,返回的结果[return 返回的值])

var result = fn(1,2); // 函数执行,返回的结果是有 return 的内容决定的
console.log(result); // => 3
复制代码

return 的做用

  1. 返回值,若是当前函数没有 return 结果出来(或者return;啥也没返回),函数执行在外面拿到的结果都是 undefined
  2. 相似于循环中的 break ,可以强制结束函数体中代码的执行(return 后面的代码再也不执行)
function fn(n,m) { 
    var total = 0; // => total:私有变量
    total = n + m;
    return total; // => 并非把 total 变量返回,返回的是变量存储的值,return 返回的永远是一个值
  	console.log('hi'); // 请问,这里会被执行吗?
}
fn(1, 2);
复制代码

上面的 console,是不会被执行的,由于,return 后面的代码再也不执行的。

不知道你们有没有看到过这样的代码:

function fn() {
	console.log('hi');
}
console.log(fn()); // 是否有返回值?返回的结果是?
复制代码

执行上面的代码,固然是有返回值,没有显示的使用 return,至关因而默认返回了 undefined。因此上面的返回结果是 undefined。

在每个 JavaScript 程序中,函数都会是你须要使用的工具,由于他们能提供的一种独特的能力,让你的代码重复使用。在编程的时候,咱们没法离开函数,不管是本身构建的函数仍是在使用 JavaScript 语言自己带有的函数。

上面讲的是函数的基本用法。后期也会深刻了解函数的运行机制、一些比较进阶的函数特性之类的。

import

咱们了解下 import 的简单用法,首先要知道什么是模块?

什么是模块?

一个模块(module)就是一个文件。一个脚本就是一个模块。就这么简单。

模块能够互相加载,并可使用特殊的指令 export 和 import 来交换功能,从另外一个模块调用一个模块的函数:

  • export 关键字标记了能够从当前模块外部访问的变量和函数
  • import 关键字容许从其余模块导入功能

好比,咱们有一个 utils.js 文件导出了一个工具函数:

// utils.js
// 格式化时间
export function formatTime(time) {
  if (typeof time !== 'number' || time < 0) {
    return time;
  }

  const hour = parseInt(time / 3600, 10);
  time %= 3600;
  const minute = parseInt(time / 60, 10);
  time = parseInt(time % 60, 10);
  const second = time;

  return ([hour, minute, second]).map(function(n) {
    n = n.toString();
    return n[1] ? n : '0' + n;
  }).join(':');
}
复制代码

而后咱们在其余模块中须要使用到这个函数,因此使用 import 进行导入使用。

// main.js
import { formatTime } from './utils.js';

formatTime(160); // "00:02:40"
复制代码

class

之前生成实例对象的传统方法,是经过构造函数。ES6 引入了 Class 这个概念,做为对象的模板。经过 class 关键字,能够定义类。

简单来讲,class 就是语法糖,糖是 sweet,让你内心甜甜的感受。能让你少写一些代码,可是只用语法糖,不学语法糖背后的原理,我是反对的。

依然以最简单的代码为例,这就是 class。

class Human{
  sayHi(){
    console.log('hi')
  }
}
复制代码

后期,我会单独写一篇文章,会结合原型相关的知识,深刻了解 class 语法糖背后的原理。

学会了 prototype,再去学 class 就跟吃糖同样简单。

参考资料

相关文章
相关标签/搜索