js基础知识

258 阅读7分钟

(个人笔记 通过阅读文章整合而成)

作用域

分为全局作用域和函数作用域

全局作用域:程序最外层的作用域,代码第一次执行时的默认环境。

函数作用域:当执行流进入函数体时才会创建,包含在父级函数作用域或者全局函数作用域内。

由于作用域的限制,每段独立的执行代码块只能访问本地作用域以及其外层作用域的变量,无权访问其内层作用域的变量

词法作用域

函数在定义的时候,其作用域就已经确定,和在哪里执行没有关系,也称为静态作用域。

块级作用域

花括号内 {...} 的区域

var是函数作用域,let和const声明的变量都是块级作用域

作用域链

当可执行代码内部访问变量时,会先查找本地作用域,如果找到目标变量即返回,否则会去父级作用域继续查找...一直找到全局作用域。这种作用域的嵌套机制,叫做作用域链。

var、let、const

1、let声明的变量是块级作用域

{
    var a=10;
    let b=0;
}
console.log(b);//ReferenceError: b is not defined

在{}外访问变量b,因此会报错。

{
    var a=10;
    let b=0;
    console.log(b);// 0
}
console.log(a);//10

这样代码就不会报错了。

2、let不存在变量提升

console.log(a);  //undefined
var a=2;
console.log(a);  //ReferenceError: Cannot access 'a' before initialization
let a=2;

var会有变量提升,因此不会报错,但是let没有,所以会报错。

3、let存在暂时性死区

var tmp;
{
    tmp="aa"; //ReferenceError: Cannot access 'tmp' before initialization
    let tmp; 
}

即使全局声明了tmp,但是{}内是块级作用域,在let声明前便使用了tmp变量,因此会报错。

4、同一作用域let不能重复声明同一个变量

let a = 10;
let a = 3;  //SyntaxError: Identifier 'a' has already been declared

5、const声明一个只读的常量。一旦声明,常量的值就不能改变。

6、const一旦声明变量,就必须立即初始化,不能留到以后赋值。

7、constlet一样,不存在变量提升,以及存在暂时性死区,只在声明所在的块级作用域内有效。

解构

按照一定模式,从数组和对象中提取值,对变量进行赋值,称为解构。

数组的解构赋值

let arr = [1, 34, 4];
let [a, b, c] = arr;
console.log(a, b, c);//1 34 4

解构赋值允许指定默认值。

let arr = [1, 34];
let [a, b, c = 232] = arr;
console.log(a, b, c); //1 34 232

对象的解构赋值

let obj = {
  a: '111',
  b: 'ewe',
};

let { a, b, c } = obj;
console.log(a, b, c);// 111 ewe undefined 

同样的,也允许指定默认值。

let obj = {
  a: '111',
  b: 'ewe',
};

let { a, b, c = 'ava' } = obj;
console.log(a, b, c); // 111 ewe ava

rest参数和扩展运算符

rest参数:...变量名

function show(a,...b){
    console.log(arguments) // [Arguments] { '0': 1, '1': 3, '2': 4 }
    console.log(b) // [3,4]
}
show(1,3,4)

rest参数与arguments对象的区别:

  • rest参数只包含了没有对应形参的实参,而arguments对象包含了传给函数的所有实参
  • rest参数是真正的数组实例(意味着可使用数组所有方法),而arguments对象不是一个真正的数组
  • arguments 对象还有一些附加的属性 (比如callee属性)。
  • rest参数之后不能再有其他参数,它只能是最后一个参数,否则会报错
  • 函数的length属性,不包括rest参数。

扩展运算符

rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

console.log(...[1, 34, 5]);//1 34 5

合并数组

var arr1 = [0, 2, 3];
var arr2 = [3, 4, 5];
arr1.push(...arr2);
console.log(arr1);//[ 0, 2, 3, 3, 4, 5 ]
var arr1 = [0, 2, 3];
var arr2 = [3, 4, 5];
var arr3 = [...arr1, ...arr2];
console.log(arr3);//[ 0, 2, 3, 3, 4, 5 ]

与数组解构赋值结合

const [a, ...b] = [1, 3, 5];
console.log(b);//[ 3, 5 ]
const [a, ...b] = [];
console.log(b); //[]
console.log(a); //undefined
const [a, ...b] = ['cy'];
console.log(a); //cy
console.log(b); //[]

如果在解构赋值中没放在最后,会报错

const [a, ...b, c] = ['cy']; // SyntaxError: Rest element must be last element
console.log(a); 
console.log(b); 

将字符串转换成数组

var str = 'nkabfk';
console.log([...str]);//[ 'n', 'k', 'a', 'b', 'f', 'k' ]

闭包

能够访问其他函数内部变量的函数。

或者说:函数内部定义的函数,被返回出去了并在外部调用 绕过了作用域的监管机制,从外部也能获取到内部作用域的信息。

function fun1(){
    var a = 0;
}
console.log(a)  //a is not defined

外层是没办法访问到a变量的,如果想要访问的话,函数内部就可以返回一个函数,比如:

function fun1(){
    var a = 0;
    return fun2(){
       console.log(a)  
    }
}
fun1()()   // 0

这样就形成了闭包。

闭包存在的问题:会造成内存泄漏,就是说变量用完了之后没有及时被释放,这部分内存没有还给操作系统或者内存池。

Promise

解决异步编程问题

异步函数多层嵌套会产生回调地狱问题

  • Promise 会有三种状态

    • Pending 等待
    • Fulfilled 完成
    • Rejected 失败
  • 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;

  • Promise 中使用 resolve 和 reject 两个函数来更改状态;

  • then 方法内部做事就是根据状态判断

    • 如果状态是成功,调用成功回调函数
    • 如果状态是失败,调用失败回调函数、
let p = new Promise((resolve, reject) => {
  let a = Math.ceil(Math.random() * 10);
  if (a > 5) {
    resolve(a);
  } else {
    reject('数太小了!');
  }
});
p.then((res) => {
  console.log(res);//6
}).catch((err) => {
  console.log(err);
});

Promise.resolve()

const p4 = {
  then(resolve) {
    setTimeout(() => resolve(4), 1000);
  },
};
const p2 = new Promise((resolve) => {
  resolve(2);
});
//接收promise对象
Promise.resolve(p2).then(console.log); //2
//接收非promise对象、非thenable对象
Promise.resolve(1).then(console.log); //1
//接收thenbale对象
Promise.resolve(p4).then(console.log);//4
Promise.reject(new Error('21')).then(console.log).catch(console.log);

catch可以捕捉异常,结果如图:

屏幕截图 2021-12-30 160334.png

Promise.reject(new Error('21')).then(() => {});

不写catch或者then后,就会报错

屏幕截图002.png

Promise.all()处理多个异步任务

只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

let p1 = new Promise((resolve) => {
  resolve('success1');
});

let p2 = new Promise((resolve, reject) => {
  resolve('success2');
});

let p3 = new Promise((resolve) => {
  resolve('success3');
});

let p = Promise.all([p1, p2, p3]);

p.then((res) => {
  console.log(res); // [ 'success1', 'success2', 'success3' ]
}).catch((err) => {
  console.log(err);
});

只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

let p1 = new Promise((resolve) => {
  resolve('');
});

let p2 = new Promise((resolve, reject) => {
  reject('err');
});

let p3 = new Promise((resolve) => {
  resolve('');
});

let p = Promise.all([p1, p2, p3]);

p.then(() => {}).catch((err) => {
  console.log(err); //err
});

Promise.race();只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success1');
  }, 1000);
});

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success2');
  }, 2000);
});

let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success3');
  }, 500);
});

Promise.race([p1, p2, p3]).then((res) => {
  console.log(res); //success3
});

async和await

用同步方式,执行异步操作

await只能在async函数中使用,否则会报错 (await is only valid in async functions and the top level bodies of modules)

async函数返回的是一个Promise对象,有无值看有无return值

await后面最好是接Promise

async function fun1(params) {
  console.log(33);//33
  let res = await fun2();
  console.log(res);//3421
  console.log(11);//11
}

function fun2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(3421);
    }, 2000);
  });
}
fun1();

事件循环

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。

宏任务

  • script(整体代码)
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI render 微任务
  • process.nextTick
  • Promise
  • Async/Await(实际就是promise)
  • MutationObserver(html5新特性)

总的结论就是,执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。

async function fun1() {
  await fun2();
  console.log('fun1 end');//第四个输出
}

async function fun2() {
  console.log('fun2 end');//第一个输出
}
fun1();

console.log('aaa');//第二个输出
setTimeout(() => {
  console.log('setTimeout end');//第八个输出
  8;
}, 0);

new Promise((resolve, reject) => {
  console.log('success');//第三个输出
  resolve();
})
  .then((res) => {
    console.log('success1');//第五个输出
  })
  .then((res) => {
    console.log('success2');//第六个输出
  })
  .then((res) => {
    console.log('success3');//第七个输出
  });

运行结果: 屏幕截图003.png