变量
1. 变量声明
var 和 let
-
var是 ES5 的声明关键字- 会参与预解析并存在变量提升(变量名提前使用不会报错,只是输出 undefined 值)
- 存在于函数作用域(只要不离开函数,都一直有效,只有一份且任何地方都能改都能取)或全局作用域
- 可以重复声明
-
let是 ES6 新增的声明关键字- 不存在 var 的那种变量提升(提前使用会报错,但也会有提升,只不过通过暂时性死区阻止了提前使用)
- 加入了块级作用域(离开了 for 或者 if 语句,就失效了,循环过程中的每一个块里面都会有一份新的变量)
- 不允许重复声明
-
例子
for (let i = 0; i < 5; i++) {} console.log(i); //输出i is undefined- 如果改成 var 的话,函数内一直有效,到外面也有效,就能输出 5,但这样子就产生了变量污染
var arr = []; for (let i = 0; i < 5; i++) { //把函数存到数组中,等待循环结束后调用 arr[i] = function () { console.log(i); }; } arr[1](); //输出结果是1,因为有闭包,且每次i都是新生成的一份- 如果改成 var 的话,函数内一直有效,只有一份,任何地方都能改都能取,就全都输出循环结束后的 5
const
-
定义一个只读的常量,后面不能再改
const y;- 这样是错的,一旦声明了就必须要赋值
-
如果定义的是对象
const obj = {}; obj.id = 123;- 不会报错!对象里面的属性还是能增删改的,只是指向的对象引用不能再改
-
如果定义的是数组
const arr = []; arr.push(1); arr[1] = 2;- 不会报错!与上面对象同理
2. 变量类型
6 大基本类型(primitive values)
-
先讲前 4 种
基本类型 对应的类 含义 number Number 数字 string String 字符串 boolean Boolean 布尔值 undefined 无 未定义 -
【创建方式 1】
字面量,视为基本类型,必须用typeof判断let count1 = 0; console.log(typeof count1); //"number" let name1 = "Tom"; console.log(typeof name1); //"string" let flag1 = false; console.log(typeof flag1); //"boolean" let a; console.log(typeof a); //"undefined" -
【创建方式 2】
new 对应的类,视为类的实例,必须用instance of判断let count2 = new Number(0); console.log(name2 instanceof Number); //true let name2 = new String("Tom"); console.log(name2 instanceof String); //true let flag2 = new Boolean(false); console.log(name2 instanceof Number); //true -
注意:用不同方式声明的变量,必须要用对应的方式判断才能得到 true 的结果
-
-
再讲 1 种(较特殊)
基本类型 含义 null 空 - 为什么说它特殊呢?
- 因为 typeof 的结果并不是 null,而是 object,但它本质上并不是一个对象,而是一种内置的基本类型
- 【创建方式】
字面量let id = null; console.log(typeof id); //"object" console.log(id instanceof Object); //false - 可以理解为,最初设计时
typeof null就被定义为返回"object",因为它具有 0 的开头,跟 object 对应的类型定义刚好吻合
- 为什么说它特殊呢?
-
再讲 1 种(ES6 新增)
基本类型 含义 symbol 符号 - 【创建方式】调用 Symbol 函数
let s = Symbol("abc"); console.log(typeof s); //"symbol" - 每一个都是独一无二的,不可能相等
- 【创建方式】调用 Symbol 函数
5 大引用类型(reference values)
-
先讲前 2 种
引用类型 对应的类 含义 object Object 对象 function Function 函数 -
【创建方式 1】
字面量let obj1 = {}; console.log(typeof obj1); //"object" console.log(obj1 instanceof Object); //true let func1 = (a, b) => { return a + b; }; console.log(typeof func1); //"function" console.log(func1 instanceof Function); //true -
【创建方式 2】
new 对应的类let obj2 = new Object(); console.log(typeof obj2); //"object" console.log(obj2 instanceof Object); //true let func2 = new Function("a", "b", "return a + b;"); console.log(typeof func2); //function console.log(func2 instanceof Function); //true -
typeof判断和instance of判断均可用,二者等价
-
-
再讲 3 种(较特殊)
引用类型 对应类 含义 无 Array 数组 无 Date 日期 无 RegExp 正则 -
为什么说特殊呢?
- 因为 typeof 的结果均为 object,它们本质上就是一个对象
-
【创建方式 1】
字面量let arr1 = [1, 2, 3]; console.log(typeof arr1); //"object" console.log(arr1 instanceof Array); //true let r1 = /[a-z]:\\s/i; console.log(typeof r1); //"object" console.log(r1 instanceof RegExp); //true -
【创建方式 2】
new 对应的类let arr2 = new Array(1, 2, 3); console.log(typeof arr2); //"object" console.log(arr2 instanceof Array); //true let r2 = new RegExp("[a-z]\\s", "i"); console.log(typeof r2); //"object" console.log(r2 instanceof RegExp); //true let d1 = new Date("December 25, 2020"); //仅支持new console.log(typeof d1); //"object" console.log(d1 instanceof Date); //true -
typeof判断和instance of判断均可用,后者更具体
-
3. 类型判断
利用 Object 上的 toString 方法
-
原理
- 每一个继承自 Object 的对象,都会从原型链中继承到其中的 toString 方法
- 如果子类对象没有重写 toString 方法,那就会直接调用继承的,返回
[Object type],这里 type 就是类型 - 但大多数对象,都重写了 toString 方法,直接返回的是代表值的字符串,所以必须显式调用 Object 上的方法(注意,这里并没有改变 this,只是改变了调用的方法)
- 如果子类对象没有重写 toString 方法,那就会直接调用继承的,返回
- 每一个继承自 Object 的对象,都会从原型链中继承到其中的 toString 方法
-
解释原理的例子
const arr = [1, 2]; arr.toString === Object.prototype.toString; // false, 所以两者不同,实际上数组上重写了 toString 方法 const o = { o: 1 }; o.toString === Object.prototype.toString; // true, 所以对象默认不需要如此调用。但如果将对象的方法改写就不一定了 o.toString = function changedToString() { return "haha"; }; o.toString(); // 'haha' Object.prototype.toString.call(o); // '[object Object]'. 发现 Object.prototype.toString 也是可以被改写的... -
判断数组的例子
const an = ["Hello", "An"]; an.toString(); // "Hello,An" Object.prototype.toString.call(an); // "[object Array]"- 经常会再链式调用一个
.slice(8, -1), 直接拿到Array五个字母
- 经常会再链式调用一个
-
其他类型的例子
Object.prototype.toString.call("An"); // "[object String]" Object.prototype.toString.call(1); // "[object Number]" Object.prototype.toString.call(Symbol(1)); // "[object Symbol]" Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call(undefined); // "[object Undefined]" Object.prototype.toString.call(function () {}); // "[object Function]" Object.prototype.toString.call({ name: "An" }); // "[object Object]"
利用 instanceof 判断对象具体类型
-
用法
对象 instanceof 类型; -
原理
- 判断对象的原型链中是不是能找到类型的 prototype
-
注意
- 只能用来判断对象类型,原始类型不可以
所有对象类型 instanceof Object都是 true
-
判断数组的例子
[] instanceof Array; // true
利用 Array.isArray()判断数组
-
原理
- ES5 新增的一个方法,当不存在时,可以 polyfill 为 toString
if (!Array.isArray) { Array.isArray = function (arg) { return Object.prototype.toString.call(arg) === "[object Array]"; }; }
- ES5 新增的一个方法,当不存在时,可以 polyfill 为 toString
-
注意
-
当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes
var iframe = document.createElement("iframe"); document.body.appendChild(iframe); xArray = window.frames[window.frames.length - 1].Array; var arr = new xArray(1, 2, 3); // [1,2,3] // Correctly checking for Array Array.isArray(arr); // true Object.prototype.toString.call(arr); // true // Considered harmful, because doesn't work though iframes arr instanceof Array; // false
-
4. 函数形态
两种简单形态
-
声明
function func1() { console.log("声明"); }- 特性: 具备提升的效果,可以在这几行之前就进行调用
- 调用
func1();
-
表达式
let func2 = function () { console.log("表达式"); }; let func2 = () => { console.log("表达式"); };- 不会提升
- 调用
func2(); - 表达式一般可以和箭头函数一起用,但会有弊端:
- 丧失 arguments 对象
- 不能作为构造函数
-
以上两种都可以嵌套到新的函数里,构成
嵌套形态
两种复杂形态
-
嵌套形态
- 自行组合
-
闭包形态
let func3 = () => { let a = "123"; return () => { console.log("闭包"); }; };- 调用
func3()();
- 调用
IIFE 立即执行
-
匿名调用
(function () {})();- 定义 和 调用 放在一起,不用起名字
-
有名调用——可以用递归
(function func(i) { console.log(i) if i < 3 { func(i++) } })(1) -
【不建议】匿名调用其实也可以用递归
(function (i) { console.log(i) if i < 3 { arguments.callee(i++) } })(1)- 在严格模式下,ES5 已经禁止了这种用法
5. 函数参数
形参
-
明确一点: 形参永远都是按值传递
-
对于基本类型,传递给形参的是原始值
-
在里面改动是无效的
-
错误示范
let swap = (a, b) => { let temp = a; a = b; b = temp; }; let a = 1, b = 2; swap(a, b); //交换 console.log(a); //1 没有变化 console.log(b); //2 没有变化
-
-
-
对于引用类型,传递给形参的也是值,只不过这个值是一个内存地址
-
直接改动这个值也是无效的
- 错误示范
let twice = (arr) => { arr = [2, 4]; }; let arr = [1, 2]; twice(arr); console.log(arr); //[1, 2] 没有变化
- 错误示范
-
但间接改动是有效的,你可以对 arr 内部进行操作变换,相当于对内存地址指向的区域进行改动
- 正确示范
let twice = (arr) => { for (let i = 0; i < arr.length; i++) { arr[i] = arr[i] * 2; } }; let arr = [1, 2]; twice(arr); console.log(arr); //[2, 4] 成功
- 正确示范
-