js 学习笔记 -- js基础知识

js

变量,类型,计算

类型

值类型:字符串,数字,bool,Symbol

引用类型:object,array,function,null

typeof运算符

typeof能识别所有值类型,识别函数,判断是否是引用类型(不可再细分)

拷贝

  1. 浅拷贝 -- 改变拷贝对象的值,原值改变
  2. 深拷贝 -- 改变拷贝对象的值,原值不改变

如何实现深拷贝:

  • 判断是值类型还是引用类型
  • 判断是数组还是对象
  • 递归

判断和计算

运算符:

当加号运算符时,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

  1. 执行async函数,返回的是Promise对象
  2. await相当于Promise的then
  3. 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渲染,检查宏任务队列为空,执行结束
posted @ 2020-10-01 20:44  S&L·chuck  阅读(227)  评论(0编辑  收藏  举报