目录
Object.prototype.toString.call()
数据类型
值类型(基本类型)
字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol、Bigint。
引用数据类型(对象类型)
对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)。
更多内容,参见:JavaScript 数据类型 | 菜鸟教程
————————————————
数据类型判断
判断数据类型的⽅法⼤概有四种:
- typeof
- instanceof
- constructor
- Object.prototype.toString.call()
typeof
- typeof对于判断诸如字符串、数字以及布尔这种基本数据来说无疑是十分简单好用的方法,但它不可以用来判断null以及除函数以外的引用类型,二者返回的都是object。
- 不建议使用typeof判断引用数据类型。
- 使用方式为typeof var
- 返回值类型为小写的字符串形式(如'string')。
typeof 'a'; // string 有效
typeof 1; // number 有效
typeof true; //boolean 有效
typeof Symbol(); // symbol 有效
typeof undefined; //undefined 有效
typeof new Function(); // function 有效
typeof null; //object 无效
typeof [1]; //object 无效
typeof new RegExp(); //object 无效
typeof new Date(); //object 无效
instanceof
- instanceof ⽤于判断⼀个变量是否某个对象的实例。
- 不可以用来判断null和undefined,二者只会返回false。这是因为它们的类型就是⾃⼰本⾝,并不是Object创建出来它们,所以返回了false。
- 对于number,string,boolean等等字面量值,只有通过构造函数定义才能检测出。
比如
let num = new Number(1);
let str = new String('syc');
let boolean = new Boolean(false);
如果只是单纯的let num = 1; 这样定义是检测不出来的。
- 所以不建议使用instanceof判断基本数据类型。
- 使用方式为var instanceof Number
- 正常情况下返回值为true或者false。
1 instanceof Number, //false
'syc' instanceof String, //false
false instanceof Boolean, //false
undefined instanceof Object, //false
null instanceof Object, //false
Symbol() instanceof Symbol, //false
[1,2,3] instanceof Array, //true
{a:1,b:2,c:3} instanceof Object, //true
function(){console.log('aaa');} instanceof Function, //true
new Date() instanceof Date, //true
/^[a-zA-Z]{5,20}$/ instanceof RegExp, //true
new Error() instanceof Error //true
注意!不可以直接使用诸如如下情况进行判断,会给你报错
undefined instanceof Undefined,
null instanceof Null
constructor
- 对于instanceof的弊端,constructor有良好的改善,它可以用来判断基本数据类型。
- constructor 属性返回对创建此对象的数组函数的引⽤。
- 在JavaScript中,每个具有原型的对象都会⾃动获得constructor属性
- 所以同样不是由Object创建出来的null和undefined不可以用constructor判断。
- 使用方式为var.constructor
- 正常情况下返回值为首字母大写的数据类型(如String)
var num = 123;
var str = 'syc';
var bool = true;
var sym = Symbol();
var arr = [1, 2, 3, 4];
var json = {name:'Shi Yechen', age:21};
var func = function(){ console.log('this is function'); }
var und = undefined;
var nul = null;
var date = new Date();
var reg = /^[a-zA-Z]{5,20}$/;
var error= new Error();
function Person(){
}
var tom = new Person();
// undefined和null没有constructor属性
console.log(
tom.constructor == Person,
num.constructor == Number,
str.constructor == String,
bool.constructor == Boolean,
sym.constructor == Symbol,
arr.constructor == Array,
json.constructor == Object,
func.constructor == Function,
date.constructor == Date,
reg.constructor == RegExp,
error.constructor== Error
);
//所有结果均为true
注意!如果使用constructor编写一个判断数据类型的函数,记得使用try()catch()方法添加一个错误处理,因为如果判断的类型为undefined或者null,会给你报错
Object.prototype.toString.call()
- 对于 Object 对象,直接调用 toString() 就能返回 '[object Object]' 。
- 对于其他对象,则需要通过.call()来调用才能返回正确的类型信息。
- 所以Object.prototype.toString.call()简直是好用到没朋友的方法。
- 使用方法为Object.prototype.toString.call(var)
- 返回值为诸如[object Object]的字符串类型(toString的意思就是返回字符串)
var toString = Object.prototype.toString;
toString.call(21); //"[object Number]"
toString.call('syc'); //"[object String]"
toString.call(true); //"[object Boolean]"
toString.call(Symbol());//"[object Symbol]"
toString.call([1, 2, 3, 4]); //"[object Array]"
toString.call({name:'wenzi', age:25}); //"[object Object]"
toString.call(function(){ console.log('this is function'); }); //"[object Function]"
toString.call(undefined); //"[object Undefined]"
toString.call(null); //"[object Null]"
toString.call(new Date()); //"[object Date]"
toString.call(/^[a-zA-Z]{5,20}$/); //"[object RegExp]"
toString.call(new Error()); //"[object Error]"
更多内容,参见:js数据类型判断_归途风景111的博客-CSDN博客_js数据类型判断
————————————————
作用域
- 变量的作用域就是变量的有效范围。
- 有效范围分为三种:块级、函数级、全局。
- 在块级代码里定义的变量只能在这个代码块里用,函数级里面定义的变量只能在这个函数和函数下的代码块里用,全局定义的变量可以在所有地方都能用。
var a = 1;
function A() {
var b = 2;//函数级作用域
console.log(a);//1,函数级作用域可以访问到全局作用域的变量
console.log(b);//2,函数级作用域当然可以访问到函数级作用域的变量
}
A(); //必须要调用函数,不然的话只会输出一个a,根本不知道b是什么
console.log(a);//1,全局作用域当然可以访问到全局作用域的变量
console.log(b);//b is not defined,全局作用域访问不到函数级作用域的变量
ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了‘块级作用域’,可通过新增命令 let 和 const 来体现。大部分包含{}的都可以构成块级作用域,但是函数和对象不构成块级作用域(函数属于的是函数作用域而不是块级作用域,对象的话,又不能在里面let)可以理解为除function和对象外的{}内都是一个块级作用域。
function getValue(condition) {
if (condition) {
let value = "blue";
return value;
} else {
// value 在此处不可用
return null;
}
// value 在此处不可用
}
- 作用域是分层的,三种有效范围是一层一层互相包含的。内层作用域可以访问外层作用域的变量,反之则不行。
- 引用变量的时候,如果不同层级有相同名字的变量,是按照就近原则, 块级里面有这个变量,就用块级定义的,否则就往上层去找。
const a = 'syc';
function A() {
const a = 'uino';
return function B() {
console.log(a);
}
}
const b = A();
b();//uino
需要注意的是,var指令不支持块级作用域,有“变量提升”的问题, 所以我们现在很多前端编码规范都要求必须用let、const指令,禁止用var指令。
执行上下文
- 变量以及函数的上下文决定了他们可以访问哪些数据。
- 上下文中的代码在执行过程中,会创建变量对象的一个作用域链。
- 多个作用域链决定了各级上下文中的代码的访问变量和函数的顺序。
执行上下文的概念非常重要,它跟事件系统都有很深的联系
- 比如我们创建这么一个函数:
function A() {
var a;
function B() {
var b;
function C() {
var c;
}
C();
}
B();
}
A();
- 那么它们的执行上下文栈以及各自的作用域链会是这样的:
- 可以看到的是, 内层作用域可以访问外层作用域的变量,反之则不行。
作用域还有很有意思的一点是,作用域在变量被定义的时候就已经被确定了,与执行位置无关,作用域链也是一样的。
更多内容,参见:JS中的作用域(超详细,看完秒懂)_浮游18岁啦的博客-CSDN博客_js作用域
————————————————
原型链
- 所有的函数都有属性prototype,默认情况下,prototype是一个Object对象,这就叫原型。
- prototype中默认包含一个属性constructor,该属性指向函数对象本身。
- 通过构造函数得到的实例对象内部会包含一个指向构造函数的prototype对象的__proto__属性。
- 在查找对象成员时,若对象本身没有该成员,则会通过__proto__属性到构造函数的原型prototype中寻找。
- 由于原型prototype本身就是一个对象,因此,它也有__proto__,指向的规则不变;
- 这样一来,从某个对象出发,依次寻找原型的指向,将形成一个链条,该链条叫做原型链。
上图中红色线为原型链。
更多内容,参见:JS原型链_咖啡配麦片的博客-CSDN博客_js原型链
————————————————
闭包
能够访问自由变量的函数就是闭包。
上文中已经讲过,全局作用域无法直接访问到函数级作用域的变量。但是如果我们在一个函数内嵌套另一个函数,让该函数返回父函数的私有变量,再让父函数返回该函数,父函数再被全局作用域的变量引用,会怎么样?
function A() {
var a = 1;
function B() {
return a;
}
return B();
}
var b = A();
console.log(b);//1
- 可以看到的是,函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。换句话说,函数A内部的私有变量a被全局作用域访问到了。
- 闭包就是将函数内部和函数外部连接起来的一座桥梁。
- 闭包有保护 + 保存的作用。
- 闭包内的变量为私有变量,外部访问不到。所以起到了保护的作用。
- 闭包中的变量是不会被销毁的,变量所在的上下文销毁了,依旧可以进行访问,所以也有保存的作用。
但这也引出来了一个问题:
- 函数中的变量都被保存在内存中而不会被销毁,所以说内存消耗很大,会造成内存溢出。
- 解决方法之一就是在退出函数之前,将不使用的局部变量全部删除(赋空值null)。
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。
更多内容,参见:js 【详解】闭包_朝阳39的博客-CSDN博客_js闭包
————————————————
事件系统
- 事件循环机制告诉了我们JS代码的执行顺序,是指浏览器或Node的一种解决JS单线程运行时不会阻塞的一种机制。
- HTML 事件可以是浏览器行为,也可以是用户行为。
- 通常,当事件触发时可以调用你的监听函数,从而执行一些代码。
事件有三个阶段:
- 事件捕获;
- 事件过程;
- 事件冒泡。
事件绑定
事件绑定中DOM0以及DOM2的区别:
DOM0
el.onclick = function() {}
- 清除绑定:
el.onclick = null;
DOM2
el.addEventListener("click", function() {
}, false)
如果是true的话,在捕获阶段调用函数,否则在冒泡阶段调用函数。
- 清除绑定:
el.removeEventListener()
事件对象
target 和currentTarget区别:
- target是指目标源;
- currentTarget指的是当前目标源。
更多内容,参见:JavaScript基础之浏览器事件模型 | 始于清风博客
————————————————
异步
- 异步就是从主线程发射一个子线程来完成任务。
- 主线程在执行同步脚本的时候,调用某些 API 会开启工作线程来执行异步任务,工作线程和主线程会同时进行。
所有同步任务都在主线程上执行,形成一个函数调用栈(执行栈),而异步则先放到任务队列(task queue)里,任务队列又分为宏任务(macro-task)与微任务(micro-task)。
宏任务
- 宏任务包括:script(整体代码)、setTimout、setInterval、setImmediate(node.js环境)、I/O、UI交互事件
微任务
- 微任务包括:new promise().then(回调)、MutationObserver(html5新特新)、Object.observe(已废弃)、process.nextTick(node环境)
若同时存在promise和nextTick,则先执行nextTick
执行顺序
- 先执行同步代码,
- 遇到异步宏任务则将异步宏任务放入宏任务队列中,
- 遇到异步微任务则将异步微任务放入微任务队列中,
- 当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,
- 微任务执行完毕后再将异步宏任务从队列中调入主线程执行,
- 一直循环直至所有任务执行完毕。
注意!当宏任务和微任务都处于任务队列(Task Queue)中时,微任务的优先级大于宏任务,即先将微任务执行完,再执行宏任务。
setTimeout(function() {
console.log(1); //异步宏任务
})
new Promise(function(resolve) {
console.log(2); //同步任务
resolve();
}).then(function() {
console.log(3); //异步微任务
})
console.log(4); //同步任务
//输出结果:2 4 3 1
- 第一步,我们会首先去查找是否有同步任务,可以看到第5行以及第10行是同步任务。这是因为我们在new一个promise的时候会同步执行里面的log语句,这就导致它成为了一个同步语句。又因为JavaScript代码是由上及下执行的,所以会先输出2后输出4。
- 第二步,会去查找异步任务中是否有微任务待执行,刚好第8行是一个异步微任务,从微任务队列中取出任务到主线程中,然后就会输出3,微任务队列为空。
- 第三步,当同步任务以及异步微任务执行完毕后,会从宏任务队列中取出任务到主线程中。这样第2行代码也就顺理成章地执行了,输出了1,宏任务队列为空,结束。
异步的实现方法
回调函数
- 将一个函数作为传递值传入另一个函数,在主函数执行完之后,再回过头来调用并执行的函数,叫做回调函数。
- 回调函数并不是指只要一个将函数作为传递值传入另一个函数就叫回调函数,回调着重的点在于“回过头来调用”。
- 回调函数就是一种异步,当工作线程异步任务完成之后,会查看当初主线程开启工作线程的时候是否指定了回调函数。如果没有指定回调函数,则异步任务完成之后不会通知主线程。如果指定了回调函数,则将回调函数和异步任务的结果封装成一条消息放到任务队列中,等待主线程空闲时从任务队列中取出该消息并执行。
定时器可以产生异步,这是因为setTimeout() 最小时间为 4ms,即使时间设置为0毫秒。
//定义主函数,回调函数作为参数
function A(callback) {
callback();
console.log('我是主函数');
}
//定义回调函数
function B() {
setTimeout("console.log('我是回调函数')",0);//即使此时时间设置为0,也会先输出主函数
}
//调用主函数,将B传进去
A(B);
"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应。所以我们可以巧妙地利用定时器来制造一个回调函数产生异步行为。
- 假定有两个函数f1和f2,后者等待前者的执行结果。
f1();
f2();
- 如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。
function f1(callback) {
setTimeout(function() {
//f1的本体代码
callback();
}, 1000);
}
- 执行代码就变成下面这样:
f1(f2);
采用这种方法,我们可以把同步操作转换为异步操作。防止运行时间过长的f1()阻塞程序的运行。
事件驱动模式
另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
先为函数f1绑定一个事件,执行结束后立刻触发done事件,f1发生done事件,就执行f2。
发布/订阅模式
我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。
Promises对象
当然也可以使用Promises对象,毕竟Promises专门为异步编程提供统一接口。
简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:
f1().then(f2);
更具体的关于promise的内容会在下文中提及。
更多内容,参见:JS异步编程异步编程的方法和原理-简书
————————————————
数组结构
数组是一种基本的数据结构,用于按顺序存储元素的集合。但是元素可以随机存取,因为数组中的每个元素都可以通过数组索引来识别。
在JS中,数组使用构造函数Array()和运算符new创建的。一般,我们用三种不同的方式来调用Array()创建数组。
无参调用
- 第一种方式是无参调用:
var a = new Array();
- 它创建的是一个没有元素的空数组。
传递参数
- 第二种方式通过传递参数明确指定数组前n个元素的值:
var a = new Array(1, 2, 3, 45, "testing");
- 这种构造方式的每一个参数都代表了一个元素值,它可以是任意类型的。
指定数组长度
- 第三种方式通过Array()构造函数传递给它一个数值参数,这个参数指定了数组的长度:
var a = new Array (10);
更多内容,参见:数组 - 廖雪峰的官方网站
————————————————
函数定义/ 调用 -》 箭头函数
函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。
定义函数
- 在JavaScript中,定义函数的方式如下:
function 函数名(参数1,参数2,...) {
函数体;
}
- 调用函数时,按顺序传入参数即可:
- 函数名(参数1,参数2);
如果你传入的参数比定义的参数多或者少,在JavaScript中都是没问题的。
function fn(a) {
console.log(a);
}
var a = 1,
b = 2;
fn(a, b);//1
fn();//undefined
定义匿名函数
- 匿名函数的定义方式如下:
var 变量 = function 函数名(参数1,参数2,...) {
函数体;
}
- 通过变量就可以调用该函数。
箭头函数
- 箭头函数是一种声明起来相对简洁的函数,但他与一般的函数有所不同,定义方式如下:
(参数1, 参数2, …, 参数N) => { 函数声明 }
- 箭头函数没有自己的 this,它的this指向定义当前调用者的上下文(就是所谓上级上下文的this)。不适合定义一个对象的方法。但在箭头函数本身的作用域中,他不会改变当前this的指向,所以适合在map()等不需要改变this指向的方法中使用。当前关于这一点,会在下文中this一栏详细提及。
更多内容,参见:JavaScript 函数 | 菜鸟教程
————————————————
类
- 类是用于创建对象的模板。
- 我们使用 class 关键字来创建一个类,类体在一对大括号 {} 中,我们可以在大括号 {} 中定义类成员的位置,如方法或构造函数。
- 每个类中包含了一个特殊的方法 constructor(),它是类的构造函数,这种方法用于创建和初始化一个由 class 创建的对象。
- 定义好类后,我们就可以使用 new 关键字来创建对象。创建对象时会自动调用构造函数方法 constructor()。
- 类的构造方法是公有的,但是类的成员属性是私有的
class Person{ //使用 class 关键字来创建一个类
constructor(name, age, habbit) { //类的构造函数
this.name = name;
this.age = age;
this.habbit = habbit;
}
eat(){ //类的方法
console.log("到点了,吃东西");
};
}
let syc = new Person("syc", "21", "Html、Css、JavaScript"); //使用new关键字来创建对象
let kun = new Person("kun", "22", "sing、dance、rap、basketball、gene tie May");
console.log(syc.name);//syc
console.log(kun.name);//kun
syc.eat();//到点了,吃东西
kun.eat();//到点了,吃东西
可以看到的是,两个成员的name各不相同,但是他们的方法eat()是相同的。
- 我们还可以向类的方法传送一个参数:
class Person{
constructor(name, year) {
this.name = name;
this.year = year;
}
age(x) {
return x - this.year;
}
}
let date = new Date();
let year = date.getFullYear(); //通过函数调取当前年份
let syc = new Person("syc", 2001);
console.log(syc.age(year)); //21
更多内容,参见:JavaScript 类(class) | 菜鸟教程
————————————————
继承
- 继承统共有原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承六种方法
原型链继承
子类想要继承父类的属性和方法,可以将其原型对象指向父类的实例,根据原型链就可以使用到父类的方法和属性。
构造函数继承
在子类的构造函数中,执行父类的构造函数,并且为其绑定子类的this。
组合继承
组合继承,也叫做伪经典继承,指的是将原型链和借用父构造函数组合到一块,其思路是用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
原型式继承
利用一个空对象作为中介、将某个对象直接赋值给空对象构造函数的原型,其实就是使用Object.create()。
缺点:跟原型链继承一样,多个子类实例的引用类型属性指向相同,可能会纂改。
寄生式继承
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。
寄生组合式继承
结合组合继承以及寄生式继承,在这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式。
更多内容,参见:JavaScript基础之继承分类 | 始于清风博客
————————————————
this
- this从寓意上来说就是指代事物本身,可以理解是一种简写。
- this可以指代当前的运行环境。例如:window/ 函数执行上下文等.
- this的精髓在于:谁调用this就会指向谁。
- this具体的内容跟执行位置有关,跟定义位置无关(跟作用域正好相反)。
- 在无显示的调用情况下,this指向全局。
console.log(this === window); //true
函数中this
- 谁调用函数,那么this就指向谁
function myFunction(){
return this;
}
console.log(myFunction());
由于这里是全局调用的函数,因此默认的this就是全局。
- 但是箭头函数是一个例外,箭头函数没有自己的this,它的this指向定义当前调用者的上下文(就是所谓上级上下文的this)。箭头函数的this指向在被定义的时候就确定了,之后永远不会改变,就算是用call()、apply()、bind()等方法也不行。
- 正是得益于这一点,箭头函数很适合在map等不需要改变this指向的方法中定义,但是对于作为监听点击事件的函数等,箭头函数带来不改变当前this指向好处的同时,也会造成点击事件难以清除等弊端
function fn1() {
console.log(this);
let fn2 = () => {
console.log(this);
}
fn2(); //this->window
}
fn1();//this->window
//因为fn1函数中this的指向是window,所以fn2箭头函数this指向fn1函数也就是间接指向window
- 如果是多层调用的话,this指向最右侧调用者本身。
const person = {
name: 'syc',
person: {
name: 'syc2',
person: {
name: 'syc3',
getname: function() {
return this.name
}
}
}
}
console.log(person.person.person.getname()); //syc3
DOM事件下this
- 如果是通过点击事件触发的话,this指向DOM对象。
下面例子中的this绑定的就是button,点击button后,该button会消失
<button onclick = "this.style.display='none'">
点击来删除我!
</button>
实例对象中的this
- 通过关键字new生成一个实例对象后,如果构造函数的返回值是非引用对象类型,那么此时的this就是实例本身。
function user() {
this.name = 'syc'
}
const syc = new user()
console.log(syc.name); //syc
- 通过关键字new生成一个实例对象后,如果构造函数返回的是一个引用对象,那么此时的this就是返回的引用对象。
function user() {
this.name = 'syc';
return{
name: 'syc2'
}
}
const syc = new user()
console.log(syc.name); //syc2
class中的this
- 通过class生成一个实例对象后,此时的this就是实例本身。
class Person{
constructor(name) {
this.name = name;
this.age = 21;
}
sayHi() {
console.log(this)
}
}
const syc = new Person('syc')
syc.sayHi(); //syc对象
如何绑定this
- 可以通过call、apply、bind来绑定this。
call
- 使用关键字call进行函数调用,第一个参数是将要改变的this,之后参数必须一一进行传递。
- 关键字call调用函数后,函数会立即执行
- 使用方法举例:函数.call(将要改变的this, 参数1, 参数2, ……)
function getname(a, b){
return [this.name, a, b]
}
const user = {
name: 'syc'
}
console.log(getname.call(user, 1, 2)) //[ 'syc', 1, 2 ]
apply
- 使用关键字apply进行函数调用,使用方法与apply类似第一个参数是将要改变的this,之后的参数是数组的形式赋值给第二个参数
- 关键字apply调用函数后,函数会立即执行
- 使用方法举例:函数.apply(将要改变的this, [参数1, 参数2, ……])
function getname(a, b){
return [this.name, a, b]
}
const user = {
name: 'syc'
}
console.log(getname.apply(user, [1, 2])) //[ 'syc', 1, 2 ]
bind
- 使用关键字bind进行函数调用,第一个参数是将要改变的this,之后参数进行一一赋值
- 关键字bind调用函数后,会生成一个新的函数,调用此函数才会执行,而且此时也是可以传递函数。
- 使用方法举例:函数.bind(将要改变的this, 参数1, 参数2, ……)
function getname(a, b){
return [this.name, a, b]
}
const user = {
name: 'syc'
}
const fn = getname.bind(user, 1)
console.log(fn(2)) //[ 'syc', 1, 2 ]
更多内容,参见:JavaScript基础之this详解 | 始于清风博客
————————————————
变量定义 (作用域提升等)
- JavaScript 变量可用于存放值(比如 x = 5)和表达式(比如 z = x + y)。
变量的命名
- 变量必须以字母开头。
- 变量也能以 $ 和 _ 符号开头(不过不推荐这么做),甚至你也可以用中文进行命名,但是最好不要给自己找麻烦
var $五 = 5;
var _六 = 6;
var 十一 = $五 + _六;
console.log(十一); //11
至于为什么最好不要用中文命名……且先不说中文有那么多长得像的字符,就是某些国家的文字,也有可能跟汉字混淆
var ㄦ = 1;
var 儿 = 2;
console.log(ㄦ); //1
console.log(儿); //2
- 变量名称对大小写敏感(y 和 Y 是不同的变量)
变量提升
- 当栈内存(作用域)形成,JavaScript代码自上而下执行之前,浏览器首先会把所有带var/function的关键字的进行提前声明或者定义,这种预先处理机制称之为变量提升。
- 也就是说函数及变量的声明都将被提升到函数的最顶部。
let y=0;
{
console.log(a);//undefined
console.log(b());//2
console.log(y) //y变量没有变量提升,所以此处找不到y,报错(此现象称为:暂时性死区)
let y = 6;
}
var a = 1; //初始化了a,只有var a声明语句提升了,a = 1初始化语句没有提升,a为undefined
function b() {
return 2;
}
- 由上面的例子可以看到,JavaScript 只有声明的变量会提升,初始化的不会。
- JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。
- let和const不存在变量提升,这也被称作暂时性死区。
变量提升中的重名问题
- 关于重名问题,变量提升时会覆盖掉前面的赋值,但是不会重复声明。
fn(); // 2
function fn() { console.log(1) };
fn(); // 2
var fn = 100;
fn(); //Uncaught TypeError: fn is not a function
function fn() { console.log(2) };
- 首先进行变量提升,三个变量都会提升,重名的话后面的变量覆盖前面的;所以fn()的值为
function fn() { console.log(2) };
- 然后执行代码,运行到fn()输出2。由于运行阶段function不会重新定义,所以第二个fn()输出值也是2。直到变量var fn = 100;直接给人家数据类型都改了,所以最后会报错。
更多内容,参见:JS中的变量提升-简书
————————————————
let 、const和var的区别
- var与let都可以声明变量,而const可以声明特殊的变量。const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。
const obj = {a:1,b:2};
console.log(obj.a); //1
obj.a = 3;
console.log(obj.a); //3
- var允许重复声明,let、const不允许。
var a = 0;
var a = 1;
let b = 0;
let b = 1; //报错,Identifier 'b' has already been declared,b不可以重复声明
const c = 0;
const c = 0; //报错,Identifier 'c' has already been declared,c不可以重复声明
- var会提升变量的声明到作用域的顶部,但let和const不会(说白了就是let和const没有变量提升)。
let b = 0;
const c = 0;
{
console.log(a); //undefined
console.log(b) //b变量没有变量提升,所以此处找不到y,报错
console.log(c) //c变量没有变量提升,所以此处找不到y,报错
let b = 6;
const c = 0;
}
var a = 1;
- 在使用let、const命令声明变量之前,该变量都是不可用的。只要作用域内存在let、const,它们所声明的变量或常量就会自动“绑定”这个区域,不再受外部作用域的影响。var没有暂时性死区。
- 全局作用域中,var声明的变量,通过function声明的函数,会自动变为window对象的变量,属性或方法,但const和let不会。
- var没有块级作用域,let和const有块级作用域。
for (var i = 0; i < 2; i++) {
}
console.log(i);//2,var没有块级作用域
for (let i = 0; i < 2; i++) {
const a = 1;
}
console.log(a);//报错,const有块级作用域
console.log(i);//报错,let有块级作用域
- 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
区别 | var | let | const |
---|---|---|---|
是否有块级作用域 | × | √ | √ |
是否存在变量提升 | √ | × | × |
是否添加全局属性 | √ | × | × |
能否重复声明变量 | √ | × | × |
是否存在暂时性死区 | × | √ | √ |
是否必须设置初始值 | × | × | √ |
能否改变指针指向 | √ | √ | × |
最后,因为let和const是es6的新特性,let和const的出现就是为了解决var的各种问题,所以建议大家写js代码都用let和const声明变量和常量。
更多内容,参见:let、const和var的区别(涉及块级作用域)_拖拉机本拉基的博客-CSDN博客
————————————————
promise
- promise是一个对异步操作进行封装并返回其结果的构造函数。
- promise的参数是一个函数,函数里有两个参数,这两个参数也是函数。res和rej这两个参数都是函数,名字随意。
new Promise(function (resolve, reject) {
// 要做的事情...
});
- res是成功时的回调
- rej是失败时的回调
- new Promise中的函数是同步函数
new Promise(function(resolve) {
console.log(2); //同步任务
resolve();
}).then(function() {
console.log(3); //异步微任务
})
console.log(4); //同步任务
//输出结果:2 4 3
- promise共有三种状态,pending,fulfilled,rejected
- 状态只能从pending变为fulfilled状态,成功状态。
- 或者从pending变为rejected状态,失败状态。
- 一旦状态改变,不可再次更改。
promise状态
- 刚开始时,promise是pedning态
const p = new Promise(function (resolve, reject) {
})
- 当你调用res函数,状态立马变为fulfilled(成功状态)
const p = new Promise(function (resolve, reject) {
resolve();
})
- 当你调用rej函数,状态立马变为rejected(失败状态)
const p = new Promise(function (resolve, reject) {
reject();
})
- 如果两个状态都存在,以谁先调用为准。
const p = new Promise(function (resolve, reject) {
resolve();
reject();
})
- PromiseValue就是res或rej执行后的返回的结果,是返给then的。
const p = new Promise(function (resolve, reject) {
resolve(1111);
})
promise参数传递
- 当你new一个promise时,就已经开始自动执行函数。promise是同步的,但then是异步的
- then函数里的参数是两个异步回调函数,第一次参数函数用于接收res返回来的数据,第二个参数函数用于接收rej返回的数据。
- then的第一个参数函数里的形参用来接收res返回的数据。
const p = new Promise(function (resolve, reject) {
console.log(1111); //1111
resolve(2222);
}).then(
function res(resolve) {
console.log(resolve); //2222
},
function rej(reject) {
console.log(reject);
}
)
- then的第二个参数函数里的形参用来接收rej返回的数据。
const p = new Promise(function (resolve, reject) {
console.log(1111); //1111
reject(3333);
}).then(
function res(resolve) {
console.log(resolve);
},
function rej(reject) {
console.log(reject); //3333
}
)
- 由于状态改变后不可再次改变,如果reject与resolve都存在的话,同样还是谁先执行就是什么态。
const p = new Promise(function (resolve, reject) {
console.log(1111); //1111
reject(3333); //由于这时候promise已经变为失败态了,所以下面成功态的语句不会执行
resolve(2222);
}).then(
(res) => {
console.log(res); //promise没有成功,不会输出该语句
},
(rej) => {
console.log(rej); //3333
}
)
- reject 通常是出现异常时所调用的,通过throw主动抛出错误或者代码出现错误,则promise的状态为rejected,值为throw的值
const p = new Promise(function (resolve, reject) {
throw "Error";
}).then(
function res(resolve) {
console.log(1111);
},
function rej(err) { //这里err传进来的参数就是我们抛出的"Error"
console.log(err);
}
)
或者代码出错也为rejected状态,例如输出一个不存在的a;console.log(a)。
const p = new Promise(function (resolve, reject) {
try{
console.log(a);
console.log("我不会输出的,不要找了")
}catch(error){
throw(error.name + ":" + error.message )
}
}).then(
function res(resolve) {
console.log(1111);
},
function rej(err) {
console.log(err);
}
)
- 如果说一个promise对象后面的then里面又新建了另一个promise对象,那么下面的then(p.then的then)的promise实例状态是根据p.then的返回值决定的
const p = new Promise(function (resolve, reject) {
resolve(1111)
}).then(
function res(resolve) {
//return 1 下个then实例的状态为fulfilled态
//return undefined 下个then实例的状态为undefined态
return new Promise(function (res, rej) {
//res() 下个then实例的状态为fulfilled态
rej("我是失败态,因为我的上一个promise传给我的就是失败态")// 下个then实例的状态为rejected态
})
},
function rej(err) {
console.log(err);
}
).then(
(res) => {
console.log(res);
},
(rej) => {
console.log(rej); //我是失败态,因为我的上一个promise传给我的就是失败态
}
)
更多内容,参见:js之promise_火红_的博客-CSDN博客_js promise
————————————————
代码规范
变量命名与代码缩进
- 变量命名使用驼峰法(除第一个单词外首字母均大写,如myClass、isNumber)
- 变量名不要以 $ 作为开始标记,会与很多 JavaScript 库冲突。
- 通常运算符 ( = + - * / ) 前后需要添加空格
var x = y + z;
var values = ["I", "Love", "Uino"];
- 代码缩进:通常使用 2 个空格符号来缩进代码块
语句规范
简单语句:
一条语句通常以分号作为结束符
var a = 0;
复杂语句:
- 将左花括号放在第一行的结尾。
- 左花括号前添加一空格。
- 将右花括号独立放在一行。
- 不要以分号结束一个复杂的声明。!!牢记
if (time < 18 && time > 9) {
greeting = "好好工作";
} else {
greeting = "下班了";
}
对象定义的规则
- 将左花括号与类名放在同一行。
- 冒号与属性值间有个空格。
- 字符串使用双引号,数字不需要。
- 最后一个属性-值对后面不要添加逗号。
- 将右花括号独立放在一行,并以分号作为结束符号。
var person = {
firstName: "Yechen",
lastName: "Shi",
age: 21,
eyeColor: "brown"
};
- 为了便于阅读每行字符建议小于数 80 个。
- 如果一个 JavaScript 语句超过了 80 个字符,建议在运算符或者逗号后换行。
document.getElementById("demo").innerHTML =
"Hello Uino";
HTML 载入外部 JavaScript 文件
- 使用简洁的格式载入 JavaScript 文件 ( type 属性不是必须的):
- 建议统一使用小写的文件名。
<script src="myscript.js">
更多内容,参见:JavaScript 代码规范 | 菜鸟教程