js
变量,类型,计算
类型
值类型:字符串,数字,bool,Symbol
引用类型:object,array,function,null
typeof运算符
typeof能识别所有值类型,识别函数,判断是否是引用类型(不可再细分)
拷贝
- 浅拷贝 -- 改变拷贝对象的值,原值改变
- 深拷贝 -- 改变拷贝对象的值,原值不改变
如何实现深拷贝:
- 判断是值类型还是引用类型
- 判断是数组还是对象
- 递归
判断和计算
运算符:
当加号运算符时,String和其他类型时,其他类型都会转为 String;其他情况,都转化为Number类型 , 注: undefined 转化为Number是 为’NaN‘, 任何Number与NaN相加都为NaN。
其他运算符时, 基本类型都转换为 Number,String类型的带有字符的比如: '1a' ,'a1' 转化为 NaN 与undefined 一样。
当object与基本类型,Number类型会先调用valueOf(), String类型会先调用toString(), 如果结果是原始值,则返回原始值,否则继续用toString 或 valueOf(),继续计算,如果结果还不是原始值,则抛出一个类型错误
布尔转化:
!!完全等同于Boolean()
除了 == null之外,其他都一律用 ===
类型比较:
如图所示:
对象和布尔值进行比较时,对象先转换为字符串,然后再转换为数字,布尔值直接转换为数字
对象和字符串进行比较时,对象转换为字符串,然后两者进行比较。
对象和数字进行比较时,对象先转换为字符串,然后转换为数字,再和数字进行比较。
字符串和数字进行比较时,字符串转换成数字,二者再比较。
字符串和布尔值进行比较时,二者全部转换成数值再比较。
布尔值和数字进行比较时,布尔转换为数字,二者比较。
原型和原型链
类和继承
class people {
name;
constructor(name){
this.name = name
}
say() {
console.log(this.name)
}
}
class student extends people{
age;
constructor(name,age){
super(name)
this.age = age
}
say() {
console.log(this.age)
}
}
let a = new student("chuck",10)
a.say()
console.log(a.name)
console.log(a.__proto__.name)
原型链
- 关系
每个 class 都有显式原型 prototype
每个实例都有隐式原型 proto
实例的 proto 指向对应 class 的 prototype
- 基于原型的执行规则
对象的成员查找机制依靠原型链:当访问一个对象的属性(或方法)时,首先查找这个对象自身有没有该属性;如果没有就找它的原型;如果还没有就找原型对象的原型;以此类推一直找到null为止,此时返回undefined。__proto__属性为查找机制提供了一条路线,一个方向。
new在执行时做了什么:
1.在内存中创建一个新的空对象实例;
2.将对象实例的__proto__属性指向构造函数的原型对象实例;
3.让构造函数里的this指向这个新的对象实例;
4.执行构造函数里的代码,给这个新对象添加成员,最后返回这个新对象。
类型判断 instanceof
A instanceof B成立的条件为
左边A的隐式__proto__链上能找到一个等于右边B的显式prototype则为true,否则为false
继承
ES6之前常用
原型对象+构造函数组合
function Person(uname, age) {
this.uname = uname;
this.age = age;
}
Person.prototype.say = function() {
return '我叫' + this.uname + ',今年' + this.age + '岁。';
};
function Student(uname, age, grade) {
Person.call(this, uname, age); // 构造函数调用父类
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype); // 原型类对象指向父类
Student.prototype.constructor = Student; //别忘了把constructor指回来
Student.prototype.exam = function() {
console.log('正在考试!');
};
Student.prototype.say = function() {
return Person.prototype.say.call(this) + this.grade + '学生。';
};
var stu = new Student('张三', 16, '高一');
console.log(stu.say()); //输出:我叫张三,今年16岁。高一学生。
1.首先在子构造函数中用call方法调用父构造函数,修改this指向,实现继承父类的实例属性;
2.然后修改子构造函数的prototype的指向,无论是寄生组合式继承,还是组合式继承,还是我们自己探索时的修改方式,本质都是把子类的原型链挂到父构造函数的原型对象上,从而实现子类继承父类的实例方法;
3.如果需要给子类新增实例方法,挂到子构造函数的prototype上;
4.如果子类的实例方法需要调用父类的实例方法,通过父构造函数的原型调用,但是要更改this指向。
核心就是原型对象+构造函数组合使用。只使用原型对象,子类无法继承父类的实例属性;只使用构造函数,又无法继承原型对象上的方法。
ES6 之后 使用 extends 关键字来继承。
class中,所有的属性,无论是否在contructor中指定,都会绑定到class的实例对象上
class A {
}
class B extends A {
}
let b = new B()
console.log(b.__proto__ === B.prototype)
console.log(B.prototype.__proto__ == A.prototype)
闭包
闭包是作用域应用的特殊情况,有两种表现:
1.函数作为参数被传递
2.函数作为返回值被传递
简单理解:当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
异步
JS是单线程语言,只能同时做一件事,DOM渲染时,JS执行必须停止,JS执行时,DOM渲染也必须停止
Promise
Promise三种状态:pending、fulfilled、rejected
状态变化:
1.pending-->fulfilled(成功了)
2.pending-->rejected(失败了)
状态变化是不可逆的
async/await
- 执行async函数,返回的是Promise对象
- await相当于Promise的then
- try...catch可捕获异常,代替了Promise的catch
宏任务/微任务
宏任务:setTimeout、setInterval、Ajax、I/O、UI交互事件(比如DOM事件)
微任务:Promise回调、async/await、process.nextTick(Node独有,注册函数的优先级比Promise回调函数要高)、MutaionObserver
微任务执行时机比宏任务要早(记住)
注意:script全部代码、(这个是执行栈的代码,属于同步代码),包括new Promise(function(){...})里面的代码,只有then、catch回调才是微任务
实现 Promise.All
我们可以设置一个数组,用它来存放每个 Promise 返回的数据,当该数组的长度等于 Promise 任务的个数时,说明拿到了所有的数据,此时就可以把该数组返回出去了。
然后又因为原生 Promise.all 返回的是一个 Promise,所以我们也需要返回一个 Promise,然后把结果数据放入 resolve 中返回出去。
Promise.all = promises => {
// 返回的是一个 Promise
return new Promise((resolve, reject) => {
let dataArr = new Array(promises.length)
let count = 0
for (let i = 0; i < promises.length; i++) {
// 在 .then 中收集数据,并添加 .catch,在某一个 Promise 遇到错误随时 reject。
// 这样,在最外面调用 Promise.all().catch() 时也可以 catch 错误信息
promises[i].then(res => { addData(res, i) })
.catch(err => { reject({ message: err, index: i }) })
}
function addData(data, index) {
dataArr[index] = data
count++
// 如果数据收集完了,就把收集的数据 resolve 出去
if (count === promises.length) resolve(dataArr)
}
})
}
笔试题:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function (){
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
console.log("???"); // 这一句是我自己加的,目的考察大家是否知道同步代码和微任务,迷惑大家resolve()后面是否还会执行
}).then(function() {
console.log('promise2');
})
console.log('script end');
从上到下,先是2个函数定义
再打印一个script start
看到setTimeout,里面回调函数放入宏任务队列等待执行
接着执行async1(),打印async1 start,看到await async2(),执行后打印async2,await后面的语句相当于Promise的then回调函数,所以是微任务,console.log('async1 end')放入微任务队列
执行new Promise,new Promise里面传的函数是同步代码,打印promise1,执行resolve(),后续触发的then回调是微任务,放入微任务队列,然后执行同步代码打印 ???
打印script end,同步代码执行完了
检查微任务队列,依次打印async1 end和promise2(这里指的是chrome/73+浏览器,后面会说不同)
尝试DOM渲染(如果DOM结构有变化)
检查宏任务队列,打印setTimeout
检查微任务队列为空,尝试DOM渲染,检查宏任务队列为空,执行结束