初识 JS 变量与常量、函数、作用域与闭包、模板字符串与标签函数
变量与常量
变量
所谓变量,简单来说就是一个可变的量,一个变量通常由三部分组成:
变量声明
变量使用 let 关键字来声明,需要注意,let 禁止重复声明,同一个作用域内,变量的名称是唯一值,否则控制台会报错提示该变量已被声明(var 由于没有作用域限制,不建议使用)变量名称(标识符)
标识符命名规则
1、必须使用英文字母、数字、下划线”_“、$,只允许用这四种来命名
2、变量名可以使用中文,但是一般不推荐使用
3、变量名的开头不允许是数字
标识符命名风格
1、蛇形:USERNAME,多个单词之间用下划线”\“连接,一般用于常量
2、驼峰:userName getUserName,第二个单词起首字母大写,一般用于变量跟函数
3、大驼峰:UserName,也叫帕斯卡命名,一般用在类/构造函数中变量的值
1、变量的声明是可以不赋值的,不赋值的时候默认为 undefined,即:let num;
2、第一次赋值也叫初始化,即给变量初始化一个赋值
num = 60;
3、第二次及以后重新给变量赋值,即变量值的更新或修改
num = 80;
4、变量值的清除
num = null;
一般写的时候可以将声明与初始化合二为一,即:
let num = 60;
常量
常量的要求及规范与变量基本相同,需要注意以下几点:
- 常量声明使用关键字”const”
- 常量在代码执行期不允许更新数据
- 声明时必须赋值
- 常量也是一种特殊的变量,名称除在常量中唯一外也不能与变量的名称相同
- 在代码中首选常量,除非确定该数据会发生变化
const USER_NAME = "admin";
数据类型
只有相同数据类型之间,他们的运算才具有意义,如数值加数值,会计算两个数之和,字符串加字符串会将两个字符串进行拼接等
- 例:100+100 运算
console.log(100 + 100);
- 例:“100”+”100” 运算
console.log("100" + "100");
原始类型
原始类型分为以下五种:
- 数值(number)
// 整数
console.log(typeof 100);
// 小数
console.log(typeof 100.1);
- 字符串(string)
console.log(typeof ("100" + "years old"));
- 布尔值(boolean)
// true
console.log(typeof true);
// false
console.log(typeof false);
以上三种最为常用,还有两个特殊值
- 未定义数值(undefined)
一般变量只声明未赋值时,默认是 undefined 类型
let a;
console.log(typeof a);
- 空对象(null)
当一个变量需要清空数据内容时,可以赋值一个 null,但是这里会返回一个空的对象类型(object),而不是 null 类型(没有 null 类型)
let a = null;
console.log(typeof a);
引用类型
引用类型分为以下 3 种:
- 数组
- 数组的变量是一个相关数据的引用的集合
- 可以通过变量名+成员的索引访问这个集合中的任何一个成员
- 成员的索引默认从 0 开始
- 数组的集合内可以有一个值,也可以有多个值
- 数组的集合内的成员可以是任意类型
如:
let arr = [
123,
"admin",
null,
true,
undefined,
[10001, 10002, 10003],
{ uname: "admin", pwd: "123456" },
function sum(a, b) {
return a + b;
},
];
console.log(arr);
console.log(arr[0]);
console.log(arr[2]);
对象
对象与数组类似,需要注意以下几点:- 数组的索引是从 0 开始的正整数,对象的索引是自定义的字符串或标识符
- 变量在对象中叫属性
- 变量引用时用点语法
arr.uname
- 当属性使用了非法标识符时,必须转为字符串(加入空格等)
- 当索引是字符串时,用[]直接调用
arr["my pwd"]
- 对象的简写(一):属性的简写,当一个变量与对象的作用域相同且名称也相同(变量名与对象属性相同)时,可以简写
- 对象的简写(二):方法的简写,方法就是属性的值是一个函数,方法的简化就是将”:function”去掉
- 在对象中可以使用 this 变量与当前对象绑定
- 使用 this 变量的时候需要注意,对象的方法不能使用箭头函数,因为箭头函数的 this 是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象
如:
let arr = { uname: "admin", "my pwd": 123456 };
console.log("1:");
console.log(arr);
console.log("2:" + arr.uname);
console.log("3:" + arr["my pwd"]);
console.log("---------");
// 对象的简写
// 属性的简化
let uname = "admin";
let info = {
uname: "admin",
};
console.log("4:" + info.uname);
console.log("---------");
// 以上可以简写为uname: uname,
info = {
uname: uname,
};
console.log("5:" + info.uname);
console.log("---------");
// 或者可以进一步简写为uname,
info = {
uname,
};
console.log("6:" + info.uname);
console.log("---------");
// 方法的简化
info = {
uname,
getInfo: function () {
return "欢迎:" + info.uname;
},
};
console.log("7:" + info.getInfo());
console.log("---------");
// 以上可以简写可以去掉: function()
info = {
uname,
getInfo() {
return "欢迎:" + info.uname;
},
};
console.log("8:" + info.getInfo());
console.log("---------");
// this
//此时this指向的就是info1
info1 = {
uname,
getInfo() {
return "欢迎:" + this.uname;
},
};
console.log("9:" + info1.getInfo());
console.log("---------");
//此时this指向的就是info2
info2 = {
uname,
getInfo() {
return "欢迎:" + this.uname;
},
};
console.log("10:" + info2.getInfo());
console.log("---------");
//此时在箭头函数中使用了this,发现出来的内容是undefined,因为此时this指向了window
info3 = {
uname,
getInfo: () => "欢迎:" + this.uname,
};
console.log("11:" + info3.getInfo());
console.log("---------");
info4 = {
uname,
getInfo: () => "本页面地址" + this.location,
};
console.log("12:window对象的location属性:" + info4.getInfo());
console.log("---------");
函数
- 函数是由 function+函数名(可选)+括号+括号里面的参数(可选)+大括号+大括号里面的内容{可选}组成的,如:
function getTotal(a,b){return a + b;}
- 函数名推荐使用动词加名称的写法
- 函数声明的时候函数名后面括号里的内容叫形参(形式参数)
getTOtal(10,20)
:调用函数是函数名+括号来调用,如果里面有参数,括号内可以传参- 函数调用时传入的参数叫实参(实际参数)
console.dir(getTotal);
:函数也是对象,也有属性跟方法,使用console.dir()
可以输出函数详情function getTotal(a,b=10){return a + b;}
:当传入的参数可能不足时,可以设置默认参数来解决function getTotal(...args){}
:当传入的参数过剩时,可以使用 rest 参数来解决,rest 参数会将所有参数压到一个数组中getTotal(...args)
:关于…:…用在函数的形参中,就是 rest,归并,用在函数的调用中就是扩展(展开)- 匿名函数(一):
(function (a,b){return a + b;})(10,20)
:所谓匿名函数就是没有名称的函数,第一种匿名函数是一次性,立即调用的函数,由(匿名函数)+(参数组成) - 匿名函数(二):
let add = function (a,b){return a + b;}
如果该匿名函数不是一次性的,应该使用函数表达式将匿名函数赋值给一个变量 - 箭头函数(一):
let add = (a,b) => {return a + b;}
:箭头函数可以简化匿名函数表达式,可以将 function 去掉直接用=>将形参跟函数内容连在一起 - 箭头函数(二):
let add = (a,b) => a + b
:如果函数的内容只有一行代码,则大括号、大括号里面的 return、大括号里面最后的;都可以省略 - 箭头函数(三):
let add = a => a + b
如果形参也只有一个,那么形参的括号也可以省略 - 箭头函数(四):
let add = () => a + b
需要特别注意如果没有形参,那么形参这里的括号一定不能省略
匿名函数开始的代码还没写
- 函数是由 function+函数名(可选)+括号+括号里面的参数(可选)+大括号+大括号里面的内容{可选}组成的,如:
示例:
function getTotal(a, b) {
return a + b;
}
console.log(getTotal(10, 20));
console.dir(getTotal);
console.log(getTotal.name);
console.log(getTotal.length);
const arr = [10, 20, 30, 40, 50, 60, 70, 80, 90];
console.log(arr);
console.log(...arr);
function getTotal(...args) {
return args.reduce((p, c) => p + c);
}
console.log(getTotal(...arr));
// 匿名函数
// 立即调用函数。IIFE
console.log(
(function (a, b) {
return a + b;
})(10, 20)
);
console.log("---------");
// 函数表达式
let add = function (a, b) {
return a + b;
};
console.log(add(20, 30));
console.log("---------");
// 箭头函数
// 常规简化
add = (a, b) => {
return a + b;
};
console.log(add(30, 40));
console.log("---------");
// 函数代码只有一行简化
add = (a, b) => a + b;
console.log(add(40, 50));
console.log("---------");
// 形参只有一个简化
add = a => a + a;
console.log(add(60));
console.log("---------");
// 无形参时简化
add = () => "hello world";
console.log(add());
作用域与闭包
作用域
- 全局作用域:在函数外面的作用域是全局作用域,全局作用域声明的变量可全局访问
- 函数作用域:函数内部的作用域,函数作用域的变量无法在函数之外访问
- 块作用域:用一个大括号包起来的作用域叫块作用域,类似于函数作用域
- 函数内部查询变量,先在函数作用域内查询,如果函数作用域内没有,就往上一级一级找,直到找到全局作用域,这个查询的链路也叫作用域链
- 作用域的内容单向传递,即只能由上一级作用域向下一级传递,不能从下一级往上一级传递
// 全局作用域
let a = 100;
let d = 200;
// 块作用域
{
let b = 300;
let d = 400;
let e = 700;
// 函数作用域
function getTotal() {
let c = 500;
let d = 600;
// 查询a时,沿着作用域链,先找当前级别是否有a,没有继续往上找,直到找到全局作用域
// 查询b时,也是沿着作用域链查找,在块作用域中找到b
// 查询c时,在本级查到,直接使用
// 查询d时,也是在本级查到,直接使用,不再往上去找块作用域跟全局作用域的d
return a + b + c + d;
}
}
// 最后结果应该是全局作用域的a+块作用域的b+函数作用域的c跟d,即100+300+500+600=1500
console.log(getTotal());
闭包
一个闭包函数,需要同时具备两个条件一个是有一个父函数,且下面必须还存在一个子函数,且存在子函数的自由变量
// 父函数
function parent(n) {
// 子函数
function son(m) {
let v = 100;
// n:子函数son的自由变量
// m:子函数son的外部参数,属于子函数son的自有变量
// v:子函数son的私有参数,属于子函数son的自有变量
return n + m + v;
}
// 外部调用parent会返回son函数
return son;
}
// 外部调用parent会返回son函数
console.log(parent(200));
console.log("---------");
// 外部调用parent后再调用son才能返回最终结果
console.log(parent(200)(300));
结合 IIFE 立即执行函数,实现点击量增加及添加一个基础浏览量的小案例
let count = (function (n) {
return function () {
return n++;
};
})(10000);
// 以上代码可用箭头函数简写为:
// let count = (n => () => n++ )(10000);
console.log(count());
console.log(count());
console.log(count());
console.log(count());
console.log(count());
console.log(count());
console.log(count());
模板字符串与标签函数
模板字符串
传统字符串多行和表达式的混写非常麻烦,使用 es6 的模板字符串可以解决该问题。
- 模板字符串使用反引号声明不用引号
- 书写多行内容遇到换行时,直接在反引号内换行即可,但是需要注意第一个换行有可能默认不会顶格,需要手动删除空格
- 使用${…}插值占位符,可以将变量或者表达式嵌入到字符串中
// 普通变量声明
let strN = "普通变量声明";
console.log(strN);
// 模板字符串变量声明
let strT = `模板字符串变量声明`;
console.log(strT);
// 普通变量换行操作
strN = "普通变量\n换行操作";
console.log(strN);
// 模板字符串变量换行操作
strT = `模板字符串变量
换行操作`;
console.log(strT);
// 普通变量拼接运算操作
const a = 10;
const b = 20;
let addN = a + "+" + b + "=" + (a + b);
console.log("普通变量拼接运算操作" + addN);
// 模板字符串变量拼接运算操作
const c = 30;
const d = 40;
let addT = `${c}+${d}=${c + d}`;
console.log("模板字符串变量拼接运算操作" + addT);
标签函数
使用模板字符串作为参数的函数就叫做标签函数。
标签函数注意:
- alert console.log 等可以直接跟模板字符串来输出内容,而不用加括号
- alert 不能直接输出变量,但是 console.log 可以
- 标签函数第一个参数是字符串中字面量组成的数组,从第二个参数起是插值表达式
- 标签函数同样支持 rest 参数
// alert+模板字符串直接输出
// alert`alert+模板字符串直接输出`;
// console.log()+模板字符串直接输出
console.log`console.log()+模板字符串直接输出`;
// alert+带变量的模板字符串直接输出
const a = 10;
// alert`alert+带变量的模板字符串直接输出:${a}`;
// console.log()+带变量的模板字符串直接输出
console.log`console.log()+带变量的模板字符串直接输出:${a}`;
// 标签函数第一个参数是字符串中字面量组成的数组,从第二个参数起是插值表达式
function getTotal(strings, num1, num2) {
return `今日进货总数为:${num1 + num2}件`;
}
console.log(getTotal`今日进货情况:小王进货${100}件,小张进货${149}件`);
//使用rest参数对不定数量的多个值进行操作
function add(strings, ...args) {
let sum = `${args.join("+")}=${args.reduce((p, c) => p + c)}`;
return sum;
}
console.log(add`我想计算:${10}${20}${30}${40}${50}${60}${70}${80}${90}之和`);