这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战javascript
了解 JavaScript 的人比比皆是,理解 JavaScript 的人寥寥无几。java
本文将带着你们一块儿深刻了解 JavaScript 基础之变量声明,废话少说,那就开始吧。es6
能够看下下面的思惟导图编程
变量声明的方式有:浏览器
咱们先来看看不使用 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';
复制代码
相信你们平时声明变量,都是使用 let 或者 const 声明,那为何还要说下 var 声明变量的优缺点呢?
之前使用 var 声明变量,多是有它存在的意义。而新特性的出现,每每都是为了解决某些问题而出现。
若是只知其然而不知其因此然,只是去背用法,但不知道为何须要这样用,不只不能融会贯通,并且也没有办法促使本身进步。
所以在下面介绍 let 和 const 以前,咱们要先了解下使用 var 声明有什么优缺点,才能理解为何会有 let 和 const 的出现。
我以为使用 var 声明变量,仍是有优势,只不过很少。好比,上面在函数里面,不使用 var 声明的变量,会覆盖以前声明的变量值。
使用 var 声明,能够解决“函数执行,覆盖以前的声明变量”的问题
function fn() {
var myName = '追梦玩家';
}
fn();
console.log(window.myName); // 请问,这个结果是?
复制代码
相信你们动手尝试下,均可以知道,这个结果是 "",而不是 “追梦玩家”。可是在全局做用域下,使用 var 声明变量,仍是至关于给 window 添加属性,那有办法解决吗?请看下面使用 var 声明的缺点。
在全局做用域下声明一个变量,也至关于给 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 跟 var 的做用差很少,但有着很是重要的区别。最明显的区别是,let 声明的范围是块级做用域,而 var 声明的范围是函数做用域。let 是在 ES6 版本出现的,其实就是为了解决使用 var 声明变量,遇到的问题,或者说是缺点。具体能够继续往下看。
能够看下下面的例子:
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 的行为与 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 关键字,建立函数,其实函数名也是变量,只不过存储的值是引用数据类型而已。
在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没有形参变量接收
复制代码
简单来讲,函数调用的时候,传递的是实参。建立的函数的时候,圆括号里面的参数,是形参。
咱们先来看看这个需求:在函数外面获取到 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 的做用
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 的简单用法,首先要知道什么是模块?
一个模块(module)就是一个文件。一个脚本就是一个模块。就这么简单。
模块能够互相加载,并可使用特殊的指令 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"
复制代码
之前生成实例对象的传统方法,是经过构造函数。ES6 引入了 Class 这个概念,做为对象的模板。经过 class 关键字,能够定义类。
简单来讲,class 就是语法糖,糖是 sweet,让你内心甜甜的感受。能让你少写一些代码,可是只用语法糖,不学语法糖背后的原理,我是反对的。
依然以最简单的代码为例,这就是 class。
class Human{
sayHi(){
console.log('hi')
}
}
复制代码
后期,我会单独写一篇文章,会结合原型相关的知识,深刻了解 class 语法糖背后的原理。
学会了 prototype,再去学 class 就跟吃糖同样简单。