JavaScript学习笔记

预解析

对于js代码,首先进行的是预解析,然后才是从上往下一行一行的执行

预解析的目的在于进行一次语法检查,以确保将执行的代码的语法无误

在预解析过程中有两个重要的动作:变量提升和函数提升

  • 变量提升

指将所有var声明的变量,提升到当前作用域最前,注意不会提升变量的赋值,所以此时该变量的值为undefined,另外如果有同名的var声明,只提升一次,之后的会被忽略

  • 函数提升

指将所有function声明的函数,提升到当前作用域的最前面,注意不会进行调用,另外如果有同名的函数,全部都会进行提升操作,所以后面的总会将前面的覆盖

fn(); //所有函数都会提升,同名函数后面的覆盖前面先提升的,所以结果为2
function fn() {
    console.log(1);
}
fn(); //2
function fn() {
    console.log(2);
}
fn(); //2
  • 注意事项
  1. 如果变量和函数同名,函数优先
console.log(a); //[Function: a]
function a() {
    console.log(1);
}
var a = 90; //会将 a 进行重新赋值,即原来预编译的结果是函数此时变成了变量
console.log(a); //90
  1. 函数提升后不会再次声明
console.log(a); //提升的结果a是个函数,所以为[Function: a]
var a = 91; //此处被覆盖为变量
console.log(a); //91
function a() { //函数在提升时已经声明,无法再次声明,即无法将变量覆盖为函数
    console.log(1);
}
console.log(a); //91
var a = 92; //再次覆盖变量
console.log(a); //92
  1. 在函数调用时一样会先对函数内部进行一次预解析

函数

  • 函数的创建
  1. 函数声明:function fn(){}
  2. 函数表达式:var fn = function(){}
  3. 具名函数表达式:var bar = function foo(){}

函数声明和表达式二者的区别在于,使用var声明的函数,在预解析时属于变量提升,即提升的只有变量,所以它是无法进行fn()进行提前调用


对于具名函数表达式,一样只能根据变量名bar去调用,而无法使用函数名foo调用,且其函数名只限于在函数内部中使用

  1. 构造函数 - new
let fn = new Function("args1", "args2", "console.log('函数代码体')"):
fn(); //函数代码体

前n个参数为该函数的形参列表,最后一个参数为函数代码体,如果只写了一个参数,则该参数表示函数代码体


这种方式可用于代码实时预览,不过最好加上try{ //... }catch(e){ //... }进行异常处理

  • 匿名函数
  • 使用方式一:赋值给变量

即函数表达式,见函数声明的第二种方式

  • 使用方式二:自调用
(function(){
    //...
})()
  • 第一个()的作用

将函数解析为函数表达式,所以他后面才可以跟()

  • 将函数解析为函数表达式的其他写法
  1. 使用()

(function(){ //... }())

  1. 使用!

!function(){ //... }()

  1. 使用+

+function(){ //... }()

  1. 使用-

-function(){ //... }()

  • 沙箱

匿名函数的自调用也被称为沙箱,沙箱内部的代码和沙箱外部的代码互不干扰,可以解决变量污染问题

  • 变量污染问题
/*张三写的一部分代码*/
var num = 12;
function fn(){ //... }
//...
/*李四写的一部分代码*/
var fn = 12;
function num(){ //... }
  • 使用沙箱方式解决
(function(){
    //张三的代码
})();
(function(){
    //李四的代码
})();
  • window参数的使用

可以用来将沙箱内的属性或方法带出去,注意一般一个js文件只会暴露一个核心功能,以避免全局污染

(function(window){
    function fn() {
        console.log("这是沙箱内的函数");
    }
    window.fn = fn;
}(window));
window.fn(); //window可省略
  • 回调函数

指一种将函数作为参数的运用方式

function foo(fn) {
    fn();
}

对象

  • 定义
  • 构造函数方式
var map = new Object();
  • 字面量方式
var map = {
    name: 'zhangsang',
    age: 12,
    say: function(){
        console.log("my name is " + this.name);
    }
}

取值

  1. 对象名. 属性名
console.log(map.name);
map.say();
  1. 使用中括号
console.log(map['age'])
  • 赋值
  1. 对象名.属性名 = 值;
  2. 对象名['属性名'] = 值;

对于赋值,已有则覆盖,无则添加

  • 批量创建对象
  • 工厂函数法
function createObj(name, age) {
    let obj = {};
    obj.name = name;
    obj.age = age;
    obj.say = function () {
        console.log("my name is " + name);
    };
    return obj;
}
let obj = createObj("abc", 12);
obj.say();

这种方式都是Object类型,无法识别更具体的类型

  • 构造函数法
function Student(name, age) {
    this.name = name;
    this.age = age;
    this.say = function () {
        console.log("my name is " + name);
    }
}
let abc = new Student("abc", 12);
abc.say();

这种方式按照规范,函数的首字母最好是***大写***的

  • new关键字的作用
  1. 会在内存中开辟一块空间,自动创建并存放一个新对象
  2. 将构造函数内部的this指向这个新创建的对象
  3. 执行构造函数,即进行赋值,然后返回赋值后的新对象
  • 判断对象的类型
  • typeof

只能用于判断简单数据类型(number,string,boolean,undefined),注意null的结果为object,此外,如果用在复杂数据类型上,结果均是object,包括函数,因为函数也属于复杂类型

  • instanceof
if (obj instanceof Student){ //... }
  • constructor
obj.constructor.name; //可以获取类型的名称
  • 遍历对象
for (var key in obj) {
    console.log(key + ': ' + obj[key])
}

在获取对象的属性前,可以先使用 in 判断下该对象是否有该属性

if ('name' in obj) { //... }

常用的内置对象

Math

是个静态类,所以使用时无需先创建对象

  1. abs:绝对值
  2. acos:反余弦
  3. asin:反正弦
  4. atan:反正切
  5. ceil:小于该数的最大整数
  6. floor:大于该数的最小整数
  7. E:算数常量e
  8. exp:常量e的x次方
  9. log、LN10、LN2、LOG10E、LOG2E
  10. max/min(num1, num2, ...):最大/小值,注意不能直接传入一个数组
  11. PI:π
  12. pow:指定数字的指定次方
  13. random:[0, 1)的伪随机数
parseInt(Math.random() * (N + 1)); //生成 0-N 的随机整数
  1. round:四舍五入
  2. sqrt:平方根
  3. SQRT2:根号二
Date

非静态类,使用前需先创建一个Date实例

  • 创建Date对象
  1. 创建当前时间

var date = new Date();

  1. 创建指定时间
var date = new Date('2080-12-09 09:18:52'); //使用字符创
var date = new Date(2080, 12, 9, 9, 18, 52); //对应年月日时分秒,注意,月份从0开始
var date = new Date(13位毫秒时间戳); //注意从1970年1月1日0点开始
  • 时间格式
  1. date.toLocaleString():显示为本地时间格式
  2. date.toLocalDateString():显示本地时间的日期部分
  3. date.toLocalTimeString():实现本地时间的时间部分
  • 指定时间格式

需要先获取日期和时间的各个部分,然后进行拼接

  • 获取时间的各个部分
  1. date.getFullYear():获取年份
  2. date.getMonth() + 1:获取月份,从0开始,故加1
  3. date.getDate():获取日
  4. date.getDay():获取星期,0表示星期天
  5. date.getHours():获取时
  6. date.getMinutes():获取分
  7. date.getSeconds():获取秒
  8. date.getMilliseconds():获取毫秒
  • 时间戳和Date的转换
  • Date转时间戳
console.log(+date);
  • 时间戳转Date
let date = new Date(时间戳);
Array

不是静态类

  1. concat:合并两个数组
var arr = arr1.concat(arr2);//原数组不变,故需进行接收
  1. join:按指定符号拼接为字符串(默认逗号)
  2. length:数组长度
  3. pop:删除并返回最后一个元素
  4. shift:删除并返回第一个元素
  5. push(ele1, ele2, ...):将一到多个元素追加到数组末尾,返回新长度
  6. unshift(ele1, ele2, ...):将一到多个元素添加到数组最前面,返回新长度
  7. slice:截取数组
  1. arr.slice();

会返回一个新数组,默认是从开始截取到最后,相当于复制(浅拷贝)

  1. arr.slice(begin);

指定截取时的开始下标

  1. arr.slice(begin, end);

指定截取时的开始下标和终止下标,注意前闭后开

  1. sort:排序

默认根据字符串的Unicode码点升序(即字典升序),注意原数组不变

  • 指定排序
new Array(11, 3, 9).sort(function(a, b) { return a - b; }); //升序
  1. reverse:反转
  2. splice:任意位置的增删操作(原数组改变)
  1. arr.splice(start)

从start下标开始删除元素,返回一个由所有被删除元素组成的数组

  1. arr.splice(start, deleteCount)

同时指定删除的个数,注意从start开始删除

  1. arr.splice(start, deleteCount, ele1, ele2, ...)

同时在start的位置上开始添加元素 ele1,ele2,…

  1. toString:转为以逗号相隔的字符串
  2. indexOf(ele):获取元素ele在数组中第一次出现的下标
  3. lastIndexOf(ele):获取元素ele在数组中最后一次出现的下标
Arguments

所有函数都自带一个 arguments 对象,作用是收集函数调用时所传递的所有参数

  • JS的可变参数,形参为空即可
function fn() {
    console.log(arguments); //[Arguments] { '0': 1, '1': [ 'abc', 'def', { key: 'value' } ] >> }
    console.log(arguments[0]); //1,可将arguments作为一个数组进行处理
}
fn(1, ["abc", "def", {"key": "value"}]);
Number
  1. toString():转为字符串
  2. toFixed(2):保留两个小数(会四舍五入)
  3. MAX_VALUE:最大数值
  4. MIN_VALUE:最小数值
  5. NEGATIVE_INFINITY:负无穷
  6. POSITIVE_INFINITY:正无穷
  7. valueOf:获取Number对象中包装的数值

原型

  • 说明

prototype属性值,任何函数都有该属性,其值是一个对象,该对象就称为原型,或原型对象

  • 作用

通过构造函数创建出来的实例对象,可以直接访问构造函数的prototype上的任意成员,换言之,可以访问原型上的任意成员

function Person(name) {
    this.name = name;
}
Person.prototype.type = "human";
let p = new Person("marry");
console.log(p.type); //human
  • 访问原型对象
  1. 可以通过构造函数(Person)的prototype属性
  2. 可以通过实例化对象§的__proto__属性
  • constructor属性

原型对象自带的一个属性,其值就是构造函数(构造器),可见,可以通过原型对象反向访问到构造函数,因此以下等式是成立的:

Person.prototype.constructor === Person; //true
p.constructor === Person; //true,p的constructor一样是来自于构造器的原型
  • __proto__属性

如同任何函数都有arguments属性,任何对象都有__proto__属性,其值就指向了构造函数的prototype属性,即原型对象,实例化对象之所以能够访问到原型对象属性正式因为如此

这是一个私有属性,因此不推荐对其进行访问和修改,另外该属性还有***兼容性问题***,在IE中,是不存在该属性的,所以尽量不要在线上代码中去使用,仅是本地开发时使用

  • hasOwnProperty方法
  • 简介

属于Object.prototype("属性名")的方法,可以用来检测一个属性是否属于对象自身,是则会返回true

  • 示例

数组自身没有push/pop等方法,是Array.prototype的方法,所以以下结果为false

[].hasOwnProperty("push"); //false

另外还可以用在对象的遍历上

for (let key in p) {
    if (p.hasOwnProperty(key)) {
        console.log(key);
    }
}
  • 原型链
  • 简介

任何一个对象,都有原型对象,而原型对象本身又是一个对象,可知原型对象也有自己的原型对象,这样对原型对象的不断追溯的结果就得到了一条由原型对象形成的链式结构,称之为原型链


原型链的尽头为Object.prototype,其值为null

  • 属性查找原则
  1. 先是会在对象自身上查找是否有该属性,有则返回
  2. 接着到对象的原型上查找,有则返回
  3. 继续查找上一级的原型对象,直到Object.prototype,此时若依旧无,则返回undefined

p -> Person.prototype -> Object.prototype

  • 属性的设置

如果该属性本身不存在,则相当于是添加该属性并进行赋值;
如果该属性存在,则相当于对该属性的值进行修改

  • 完整的原型链
  • 说明
console.log(Function.constructor); //[Function: Function]
console.log(Object.constructor); //[Function: Function]

即Object的构造器constructor是Function,Function构造器的constructor是它自身,可知所有构造器的constructor都将指向Function

console.log(Function.__proto__); //ƒ () { [native code] }
console.log(Function.__proto__.__proto__); //Object
console.log(Object.__proto__); //ƒ () { [native code] }
console.log(Function.__proto__ === Object.__proto__); //true
console.log(Function.__proto__ === Function.prototype); //true
console.log(Function.__proto__.__proto__ === Object.prototype); //true
console.log(Object.prototype.__proto__); //null
  • 可知原型链的终点是Object.prototype的原型,为null
  • Function的原型链为

Function -> 匿名函数f(){} -> Object.prototype -> null

  • Object的原型和Function的原型相同

Object.__proto__ = Function.__proto_ = Function.prototype

  • 原型三角关系
  1. 将Object视为实例对象
  • 实例对象:Object
  • 构造函数:Function
  • 原型对象:Function.prototype
  1. 将Function视为实例对象
  • 实例对象:Function
  • 构造函数:Function
  • 原型对象:Function.prototype
  1. 几个instanceof关系
Function instanceof Function; //true,由Function.__proto__ === Function.prototype可知
Function instanceof Object; //true,由Function的原型链可知
Object instanceof Object; //true,由Object.__proto__.__proto__ === Object.prototype可知
Object instanceof Function; //true,由Object.__proto__ === Function.prototype可知
  • 练习
[] == []; //false,两个对象,不同的地址
[].push == [].push; //true,都是源自同一个prototype的属性

严格模式

  • 简介

是ES5提出来的一种代码运行模式,通常会和沙箱模式一起使用,开启严格模式后,一些不规范的编程方式将会直接报错,如在一个函数的外部去使用函数内部的没有用var声明而创建的隐式全局变量

  • 开启

"use strict",表示这句话之后的代码都要遵循严格模式

  • 严格模式下对不规范编程的常见要求
  • 变量必须声明,即必须要用关键字var/let/const等声明
  • 函数参数不能同名,如function fn(p1, p2, p1)
  • 禁止使用八进制数,如var num = 012;
  • 禁止使用保留字作为变量名称,常见保留字有:
//implements, interface, let, package, private, protected, public, static, yield等
  • 注意和建议
  1. 最好是配合沙箱模式一起使用,而不是在全局中开启,原因是如jQuery等第三方js库可能并没有完全遵循严格模式,导致因为严格模式而报错
!function(window){
     "use strict";
     //...
}(window)
  1. 严格模式只会在当前作用域中有效,一般会将其写在当前作用域的最前端

继承

混入型继承
  • 介绍

这种方式简单粗暴,直接拷贝另一个对象的属性

  • 示例
let father = {
    car: "法拉利"
};
let son = {};

/*属性拷贝*/
for (let property in father) {
    son[property] = father[property];
}

/*如果有多个继承对象,可以创建一个用于继承的方法*/
son.extend = function (object) {
    for (let property in object) {
        if (object.hasOwnProperty(property)){
            this[property] = object[property];
        }
    }
};
原型链继承
  • 简介

指通过在构造函数的原型对象上去创建那些共有的属性或方法,这样该构造函数所有的实例对象都将可以使用这些属性或方法

  • 示例
function Person(){}
Person.property.color = "pink";
let p = new Person();
console.log(p.color); //pink
  • 原型替换

当需要添加的属性较多时,可以直接重写构造函数的原型对象,不过要将constructor构造器属性手动添加上

Person.property = {
    constructor: Person, //手动添加上
    color: "pink"
}
混入+原型链继承
  • 简介

指将混入型继承和原型链继承这两种继承方式进行结合使用

  • 示例
function Person() {}
let obj = {
    say: function () {
        console.log("hello");
    }
};
/*需求:让所有的Person实例都有say方法*/
Person.prototype.extend = function (object) {
    for (let property in object) {
        if (object.hasOwnProperty(property)){
            this[property] = object[property];
        }
    }
};
let p = new Person();
p.extend(obj);
p.say();
经典继承
  • 简介

ECMAScript5新增的一个方法Object.create(obj);,该方法会创建一个空对象实例({}),该空对象的原型就是参数obj,如果需要属性,则需要补充第二个参数,这里不详述

构造函数式继承
  • 简介

指通过借用构造函数的方式来实现继承

  • 示例
function Human(age, gender) {
    this.age = age;
    this.gender = gender;
}
function Chinese(age, gender, language) {
    Human.call(this, age, gender);
    this.language = language || "汉语";
}
let zhangsan = new Chinese(19, 'male');
console.log(zhangsan.age); //19
console.log(zhangsan.gender); //male
console.log(zhangsan.language); //汉语

this指向与函数的调用模式

  • 说明

每个函数都有自己的this指向

this指向和函数在哪被调用无关,而是和函数的调用模式相关,即只有在函数被调用的时候才能确定具体的指向

对于this指向的判定可以分为两步

  1. 该this属于哪个函数
  2. 该函数的调用模式是哪一种
  1. 函数调用模式
function fn(){
    console.log(this); //window
}
fn();

该模式下,this指向为window对象,可理解为因为是window调用了该方法,所以本质上和方法调用模式是一样的

  1. 方法调用模式
var obj = { 
    fn: function(){
        console.log(this); //{ fn: [Function: fn], key: 'value' } 即 obj
    },
    key: "value"
}
obj.fn(); //或 obj['fn']()

该模式下,this指向为调用该方法的对象,即obj
,

注意,let arr = [fn, 1]; arr[0]();,像这种,this指向依旧是arr,因为[].一样,都属于方法调用模式

  1. 构造函数模式
function Person(){
    console.log(this); //Person {}
}
new Person();

该模式下,this指向为构造函数的实例对象
,

注意,new jQuery.fn.init(selector);,这也属于是构造函数模式,而非方法调用模式

  1. 上下文模式/借用方法模式
  • 说明

任何函数可以使用apply, call, bind这三个方法,通过这三个方法的任一个都可以实现方法借用,即使用一个自身不存在而其他对象拥有的方法

  • apply方法
  • 语法

apply(thisArg, args)

  • 第一个参数thisArg:改变this指向为参数thisArg
  • 第二个参数args:实参列表,一个数组或伪数组
  • 示例
let arr = [1, 3, 2];
let max = Math.max.apply(arr, arr);
console.log(max); //3
  • call方法
  • 语法

call(thisArg, args1, args2, ...)

  • 第一个参数thisArg:改变this指向为参数thisArg
  • 其他参数args:和apply方法差不多,只不过将实参一个个单独列出
  • 示例
/*demo1*/
function add(n1, n2) {
    console.log(this); //{}
    console.log(n1, + n2); //3
}
add.call({}, 1, 2); //{}借用了add方法
/*demo2*/
let zhuyinqi = { //助音器对象
    say: function () {
        console.log(this.name + ": hello") //yaba: hello
    }
};
let yaba = { //哑巴
    canSay: false,
    name: "yaba"
};
zhuyinqi.say.call(yaba); //哑巴借用了助音器的say方法
  • bind方法
  • 语法

bind(thisArg)


bind方法可以创建并返回一个结构一模一样的新函数,且返回的函数,其内的this指向被固定为了thisArg,另外要注意的是,bind并不会直接调用函数

  • 示例
let fn = function(){
    console.log(this); //[1]
}
let fnBind = fn.bind([1]);
fnBind();
let obj = {
    prefix: "当前时间是:",
    sayDate: function () {
        setInterval(function () { //如果不用bind,setInterval的this指向默认是window
            console.log(this.prefix + new Date());
        }.bind(this), 1000);
    }
};
obj.sayDate();

伪数组

  • 特点

伪数组有下标、有length属性,但是不能使用数组的方法,不过可以通过 call
等方法借用的方式来使用

  • 借用push方法
let fake = {
    0: "a",
    1: "b",
    length: 2
};
[].push.call(fake, "c");
console.log(fake); //{ '0': 'a', '1': 'b', '2': 'c', length: 3 }
  • 将伪数组快速转为真数组
let fake = {
    0: "a",
    1: "b",
    length: 2
};
let real = [].slice.call(fake);
console.log(real); //[ 'a', 'b', 'c' ]

作用域

指变量的使用范围,在编程语言中有两种作用域:词法作用域和块级作用域

  • 词法作用域
  • 几个变量
  1. 全局变量

函数外声明的变量,任何地方都可以访问到

  1. 局部变量

函数内声明的变量,只有在函数内部才能使用

  1. 自由变量

对于一个函数而言,如果没有声明该变量,却使用到了这个变量,那么该变量对该函数而言就是个自由变量

let a = 20;
function fn() {
    console.log(a); //变量a对于函数fn而言就是个自由变量
}
  • 函数作用域

在js中只有函数能生成作用域,所以js中的词法作用域就是函数作用域,作用域范围在函数声明时确定,其中

  • 对于函数中使用的局部变量

直接在函数内就可以找到局部变量的值

  • 对于函数中使用的自由变量

需要去创建这个函数的作用域中去查找该自由变量的值

  • 示例
let num = 1;
function fn(){
    console.log(num);
}
fn(); //num是fn的自由变量,需要去创建函数fn的作用域中寻找,找到了let num = 1;

注意对于函数的调用,在调用时一样会先进行一次预解析

  • 作用域链

只要是函数就会形成一个自己的作用域,如果一个函数又是嵌套在另一个函数中,那么该外部函数也有自己的作用域,这样层层向上,直至全局环境,就形成了一个作用域链

  • 变量搜索原则
  1. 当前作用域是否声明了该变量,有则直接返回
  2. 往上一层作用域查询,有则返回
  3. 继续往上查询,直至全局作用域,有则返回,无则报错
  • 块级作用域

指由{}花括号构成的作用域,在该作用域中声明的变量外部无法访问

在js中不存在块级作用域,因为在外部是可以访问{}中声明的变量的,如:for(){var i = 0;},在for的外部是可以访问变量i的

在ES6中,可以使用let来模拟块级作用域,如:for(){let i = 0;},此时外部将无法访问变量i

缓存与闭包

缓存

在js中,缓存可以使用数组获取对象实现

/*斐波那契*/
let cache = {};
function fbnq(month) {
    if (month === 1 || month === 2) return 1;
    if (cache[month]) return cache[month];
    else {
        cache[month] = fbnq(month - 1) + fbnq(month - 2);
        return cache[month];
    }
}
console.log(fbnq(50)); //12586269025
闭包
  • 简介

闭包指内部函数和声明该函数的词法环境的组合,它有两个形成条件

  1. 两个或多个嵌套的函数
  2. 内部的函数必须访问外部的函数变量
  • 作用

可以保护数据安全、持久化维持数据、保护缓存数据(如斐波那契的cache变量)

  • 统计内部函数的调用次数
function outer() {
    let count = 0;
    function inner() {
        console.log(++count);
        return count;
    }
    return inner;
}

let inner = outer(); //这样在外部既可以统计次数又不会修改count的值
inner(); //1
inner(); //2

  • 问题与方案

存在内存泄漏问题,即有一块内存被一直占用着,此时需要手动释放闭包占用的内存

let out = outer();
out = null;

Promise

  • 说明

Promise是一个构造函数,是ES6提出的一个重要的语法,一种处理异步的方案,意欲以同步的方式编写异步代码,打破此前需要通过回调函数方式来处理异步的编程方式

  • 语法
const p = new Promise(callback(resolve, reject));
  • resolve:操作成功时的回调函数
  • reject:操作失败时的回调函数
const p = new Promise((resolve, reject) => {
    //这里一般放异步操作代码
    setTimeout(() => {
        console.log('异步操作开始');
    }, 1000);
});
  • Promise的三种状态
  1. resolve:任务成功时
  2. reject:任务失败时
  3. pending:等待/进行中(如resolve和reject都不执行时)
  • then 和 catch
  • 基本用法
const p = new Promise(((resolve, reject) => {
    setTimeout(() => {
        resolve("success");
        reject("error");
    }, 1000);
}));
p.then((param) => { //param对应resolve的传参,如果上面未调用resolve,比如resolve代码注释,则此处的then方法不会触发
    console.log(param); //success
});
p.catch((param) => { //param对应reject的传参,如果上面未调用reject,比如reject代码注释,则此处的catch方法不会触发
    console.log(param); //error
});
  • 链式调用:读取多个文件
/*注:以下代码需node环境*/
const fs = require('fs');
const path = require('path');

function readFile(filePath) {
    return new Promise(((resolve, reject) => {
        fs.readFile(filePath, 'utf-8', (err, data) => {
            if (err) reject(`${filePath}: fail`);
            else resolve(data);
        })
    }))
}

readFile(path.join(__dirname, '/static/file', 'a.txt'))
    .then((res) => {
        console.log(res);
        return readFile(path.join(__dirname, '/static/file', 'b.txt'))
    })
    .then((res) => {
        console.log(res);
        return readFile(path.join(__dirname, '/static/file', 'c.txt'))
    })
    .then((res) => {
        console.log(res);
    })
    .catch((err) => { //会捕获首先出现的异常
        console.log(err);
    });

  • await 和 async
  • 说明

属于ES8的语法,可以用来替换then和cache,相比之下它们无需任何回调函数,更加彻底地实现了同步编程方式

awaitasync总是成对出现,因为二者是配套使用的,并且必须是同一个函数情况下

  • await

会等待一个异步操作完成,并获取结果,要求修饰类型必须是Promise类型

  • async

一个函数关键字,用来修饰一个内部有异步操作的函数

  • 读取多个文件
/*注:需node环境*/
const fs = require('fs');
const path = require('path');

function readFile(filePath) {
    return new Promise(((resolve, reject) => {
        fs.readFile(filePath, 'utf-8', (err, data) => {
            if (err) reject(`${filePath}: fail`);
            else resolve(data);
        })
    }))
}

async function start() {
    let res1 = await readFile(path.join(__dirname, '/static/file', 'a.txt'));
    //存在隐性问题:一旦出错,后面的代码将不会继续
    console.log(res1);
    let res2 = await readFile(path.join(__dirname, '/static/file', 'b.txt'));
    console.log(res2);
    let res3 = await readFile(path.join(__dirname, '/static/file', 'c.txt'));
    console.log(res3);
}

start();

  • 使用try-catch解决该隐性问题
async function start() {
    try {
        let res1 = await readFile(path.join(__dirname, '/static/file', 'a.txt'));
        console.log(res1);
    } catch (e) {
        console.log(e)
    }
    try {
        let res2 = await readFile(path.join(__dirname, '/static/file', 'b.txt'));
        console.log(res2);
    } catch (e) {
        console.log(e)
    }
    try {
        let res3 = await readFile(path.join(__dirname, '/static/file', 'c.txt'));
        console.log(res3);
    } catch (e) {
        console.log(e)
    }
}
  • all 和 race
  • 说明
  • all

等待所有的异步任务都完成后执行then函数,该then函数的参数为所有异步结果组成的数组

const axios = require("axios");
Promise.all([
    axios.get("http://localhost:3000/one"),
    axios.get("http://localhost:3000/two"),
    axios.get("http://localhost:3000/three")
]).then(res => {
    console.log(res); //res为所有异步结果共同组成的数组
})
  • race

只要有某个异步任务完成就执行then函数

const axios = require("axios");
Promise.race([
    axios.get("http://localhost:3000/one"),
    axios.get("http://localhost:3000/two"),
    axios.get("http://localhost:3000/three")
]).then(res => {
    console.log(res);
})

事件循环机制

console.log(1);
setTimeout(function() {console.log(2);}, 0);
console.log(3);

上例的输出并非是1 2 3,而是1 3 2

  • js中的线程

js中共有3种线程:主线程、ui渲染线程、事件循环线程

  1. 主线程

单线程方式执行所有的js代码

  1. ui渲染线程

用于渲染页面

  1. 事件循环线程

查看事件队列中是否有需要执行的回调函数,有则交给主线程去执行

  • 异步代码

延时器(setTimeout)、定时器(setInterval)、事件处理函数、ajax等都属于异步代码,这些异步代码,会等其对应的时间到了的时候,将其中的回调函数放入事件队列中,最后交由事件循环线程处理并交给主线程去执行

for (let i = 0; i < 3; i++) {
    setTimeout(function(){
        console.log(i); //3 3 3
    }, 0);
}
  • 宏任务与微任务
  • 任务分类
  • 宏任务:script(主线程代码)、timeout等
  • 微任务:Promise
  • 执行顺序

先执行宏任务,再执行微任务

console.log(1);  //第一圈事件循环:宏任务,执行顺序:1
setTimeout(() => { //属于下一圈(第二圈)宏任务,执行顺序:5
    console.log(2);
}, 0);
new Promise(resolve => { //第一圈事件循环:正常的实例化代码-宏任务,执行顺序:2
   console.log(3);
   resolve(); //该方法地执行,意味着then方法将会被调用
}).then(res => { //第一圈事件循环:微任务,执行顺序:4
    console.log(4);
});
console.log(5); //第一圈事件循环:宏任务,执行顺序:3

// 1 3 5 4 2

深拷贝和浅拷贝

  • 浅拷贝

指只拷贝对象的一层属性,适用于被拷贝的对象只有简单数据类型时

for (k n source) {
    target[k] = source[k];
}
  • 深拷贝

指拷贝时会将对象的所有层级属性都进行拷贝

function deepCopy(source) {
    let target = {};
    for (k in source) {
        target[key] = typeof source[k] === "object" ? deepCopy(source[k]) : source[k];
    }
    return target;
}

ES6的几个实现

  • let

即使用let关键字来定义变量,有以下特点

  1. 不会变量提升
console.log(a); //直接报错,由let定义的变量不会进行变量提升
let a = "a";
  1. 不能重复定义
let a = "a";
let a = "aa"; //直接报错
function a(){ //... } //一样直接报错
  1. 块级作用域
  • const

特点同let,不过用于定义常量

  • 字符串API
  • str.startsWith():是否已***开头
  • str.endsWith():是否已***结尾
  • str.includes():是否包含***
  • 数组API
forEach(func(v, i, arr)); //遍历
map(func(v, i, arr) {return *** }); //映射,需接收
filter(func(v, i, arr){return bool}); //过滤,需接收
some(func(v, i, arr){return bool}); //anyMatch
every(func(v, i, arr){return bool}); //allMatch
find(func(v, i, arr){return bool}); //获取第一个满足条件的元素
findIndex(func(v, i, arr){return bool}); //获取第一个满足条件的元素的索引值
reduce(func);
  • 对象简写
  1. 对象中的属性名和变量名相同时,可以只写属性名
  2. 对象中的方法可以省略```function
let name = "xx";
let age = 29;
let obj = {
    name: name, //未省略变量名
    age, //省略变量名
    say1: function(){}, //未省略function
    say2(){} //省略function
}
  • 函数默认值
function fn (arg1=1, arg2=2) {
    //...
}
  • 箭头函数
  • 语法和简写

语法类似 java 的 lambda 表达式,不过使用的箭头是=>,简写则一样

  • this指向

箭头函数内部的this指向的是函数外部的this(即它的作用域链中的上级作用域)

let obj = {
    name: "xx",
    say() {
        setTimeout(() => {
            console.log(this.name); //xx,此时this执行的不再是window对象,而是obj对象
        }, 1000);
    }
}
  • 模块化语法

如有:main.js, a.js


import + export default

//a.js
let num = 30;
export default num;
export default num; //报错,只能export default一次

//main.js
import num from './a.js';
console.log(num); //30

import + export

//a.js
/*export导出的是一个对象*/
export let login = () => {}; //对象中将有login属性
export let register = () => {}; //对象中将有register属性

//main.js
/*非法导入方式:不能直接用一个变量名去接收*/
import res from './a.js'; //报错

/*正确导入方式一:导出所有属性*/
import * as res from './a.js';
res.login();
res.register();

/*正确导入方式二:解构方式接收指定属性*/
import {login, register as reg} from './a.js';
login();
res();

import

import axios from 'axios';

常用插件

  • memento.js:日期处理插件
  • wangEditor.js:轻量级富文本编辑器
  • require.js
  • artTemplate:模板引擎插件
  • 简介

用于渲染后台数据,替代字符串拼接方式,artTemplate是由腾讯开发的高效的模板引擎插件

  • 示例
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<button>生成button>
<div>div>

<script src="./lib/template-web.js">script>

<script type="text/html" id="demo">
    <h1>{{title}}</h1> <!--属性名可以直接引用-->
    <ul>
        {{each data value index}} <!--列表可以遍历-->
        <li>{{value}}</li>
        {{/each}}
    </ul>
script>
<script>
    let div = document.querySelector("div");
    const arr = ["java", "bigdata", "python", "web", "php"];
    /* 3. 数据准备:必须是一个js对象 */
    let obj = {
        title: "模板引擎DEMO"
    };
    /* 4. 数据和模板绑定 */
    document.querySelector("button").onclick = function () {
        obj.data = [];
        obj.data.push(arr[Math.floor(Math.random() * arr.length)]);
        div.innerHTML = template("demo", obj);
    }
script>
body>
html>
  • 注意

如果要直接对传入的对象进行遍历,则有固定写法

obj = {
    name = "aaa",
    age = 12
}
{{each $data value i}} //$data表示传入的对象自身

小知识

  • =、==、===的区别
  1. =

赋值运算符

  1. ==

判断是否相等,只进行值的比较,另外复杂类型比较的是地址

  1. ===

判断是否相等,会先进行值的比较,相等前提下再进行类型的比较

  • 隐式全局变量
function fn() {
    num = 1;
}
fn();
console.log(num); //1

fn()函数的执行,会将其中的num自动创建为一个全局变量,代码中并未使用var/let等关键字,所以称其为隐式全局变量,这种变量在严格模式下将会报错,建议尽量少用

  • undefined和null没有包装类型,所以使用toString方法会报错
  • 值类型和引用类型
  • 值类型

也称简单数据类型,在内存存储的时候存储的是值本身

  • 引用类型

也称复杂数据类型,在内存存储的时候存储的是它的引用地址

  • 存储方式

同java,但无明确的堆栈之分,统称为内存

  • parseInt
console.log(parseInt(1.99)); //1,直接取出小数部分
console.log(parseInt("1.99")); //1,如果是个非数字,则为NAN
  • a标签不跳转

  • js中的false
  1. false
  2. null
  3. undefined
  4. 0
  5. ""
  6. NaN
  • && 和 ||
  • && 的使用:函数在存在的情况下才进行调用
function foo(fn) {
    fn && fn();
}
  • || 的使用:参数默认值
function fn(arg){
    arg = arg || "默认值";
}
  • eval

有着和new Function一样的效果,即可以执行字符创代码,如:

eval("alert(1)");
eval("var num = 10; console.log(num)")

慎用,存在xss攻击的风险

  • debug
  1. source
  2. 找到需要进行调试的html文件并标记断点
  3. 刷新页面

  • 几个选项说明
  1. watch:可查看变量情况
  2. F10:让代码一步步执行
  3. F11:同F10,不过可以进入函数内部
  4. F8:跳入下一个断点
  5. shift + F11:跳出函数
  6. Deactivate breakpoints:取消所有断点
  • 文件名上传后中文名称乱码问题

要注意的是,对于上传的文件,前端request - 后端respose - 数据库,所有编码须一致,有个问题是,数据库中会将中文的unicode的\u的\丢掉,导致了中文乱码问题,此时可以设置对中文不进行unicode编码即可:

json_encode($data, JSON_UNESAPED_UNICODE);
  • 特殊的命名
    let gender = 'sex'
    let obj = {
        'name': "张三", //字符串形式的key
        age: 12, //正常情况下的key的使用方式
        [xxx]: "男" //变量形式的key,需要额外用[]包围起来才能生效
    }
    console.log(obj['name']); //张三,字符串形式的key,只能使用这种方式来取值
    console.log(obj); //{ 'name': '张三', age: 12, sex: '男性' }
  • 模板字符串

指通过使用 `` 反引号的方式来组装字符串,从而取代繁琐的字符串拼接工作,除此之外,这种方式还可以使用${}的方式来引用外部变量值

let name = "张三";
let str = `

${name}

`
;
  • console.log的监听功能
let obj = {};
console.log(obj); //这里显示的是{},但是展开后可以看到 name:张三,这是因为console.log监听到了之后obj有添加了name属性,即它会监听要打印的obj这个对象
obj.name = "张三";
console.log(obj); //这里直接显示的{name: 张三}
  • 解构

属于ES6的语法

let obj = {
    name: "张三",
    age: 19,
    account: {
        username: "zs",
        password: "123"
    }
};
!function () {
    const {name: user, age, account: {password}} = obj;
    console.log(user, age, password); //张三 19 123
}();

for中的延时器

问题:输出的全都是10

for (var i = 0; i < 10; i++){
    setTimeout(function () {
        console.log(i):
    }, i * 1000);
}

方案一:使用闭包

for (var i = 0; i < 10; i++){
    (function f(i) {
        setTimeout(function () {
            console.log(i):
        }, i * 1000);
    })(i);
}

方案二:使用let块级作用域

for (let i = 0; i < 10; i++){
    setTimeout(function () {
        console.log(i):
    }, i * 1000);
}

你可能感兴趣的:(front-end,javascript)