js基础知识小结

1、 js数据类型分为:原始类型和对象类型;
原始类型:boolean number string undefined null symbol bigint
对象类型:Object Function;
原始类型存储在栈上,对象类型存储在堆上,但是他的引用地址还是存在栈上。
?题型:对于对象的修改,往函数里传一个对象进去,函数内部修改参数,这类题目记住以下要点:
1、对象存储的是引用地址,传来传去、赋值给别人都是在传递值(存在栈上的内容),别人一旦修改对象里的属性,大家都被修改了;
2、但是对象一旦被赋值了,只要不是原对象被重新赋值,那么就永远不会修改原对象。
2、类型判断
1、typeof:原始类型中除了null,其他类型都可以通过typeof来判断。
(typeof null = obejct) => 久远的bug;
2、instanceof通过内部的原型链的方式来判断是否为构建函数的实例,用于判断具体的对象类型;
3、Object.prototype.toString 相对而言,判断类型更加完整;
Object.prototype.toString.call(1) => '[Object Number]';
Object.prototype.toString.call(null) => '[Object Null]';
....
4、特定的api
数组:Array.isArray([])
是否是非数字:isNaN(',')
3、类型转换
1、强制转换:
Number(false) => 0;
Number('1') => 1
Number('zg') => NaN
记住转换规则:
转布尔值规则:
1、undefined null false NaN ''、0、-0都转为false;
2、其他所有值都转为true,包括所有对象;
转数字规则:
1、true为1,false为0
2、null为0,undefined为NaN,symbol报错
2、隐式转换
(比较繁琐)
4、this
普通函数:
1、函数被谁调用,this就是谁,没有被对象调用,this就是window;
2、以下情况数优先级最高的,this只会绑定在c上,不会被任何方式修改this指向:
var c = new foo();
c.a = 3;
console.log(c.a)
3、利用call、bind、apply改变this指向,优先级仅次于new

箭头函数: 箭头函数没有this,所以一切妄图改变箭头函数this指向都是无效的。 箭头函数的this只取决于定义时的环境,比如如下代码中的fn箭头函数是在window环境下定义的,无论如何调用,this都指向window。 var a = 1; const fn = () => { console.log(this.a); } const obj = { fn, a: 2 } obj.fn(); // 1牢记规则。比如这道题: const a = { b: 2, foo: function() { console.log(this.b); } }function b(foo) { foo(); } b(a.foo); //undefined this => window,this.b => undefined

5、闭包
定义:假如一个函数能访问外部的变量,那么这个函数他就是一个闭包,而不是一定要返回一个函数。 ` let a = 1; //fn是闭包 function fn() { console.log(a) } function fn1() { let a = 1; return () => { console.log(a) } } const fn2 = fn1(); fn2(); 详情见:[闭包](https://juejin.cn/post/6947860760840110088#heading-18) `

6、new
new操作符可以帮助构建一个实例,并绑定到this,执行步骤如下: 1、新生成一个对象; 2、对象连接到构造函数原型上,并绑定this; 3、执行构造函数代码; 4、返回新对象;在第四步返回新对象这边有一个情况会例外: ` function Test(name) { this.name = name; console.log(this); Test {name: 'yck'} return { age: 26 } }const t = new Test('yck'); console.log(t); // {age: 26} console.log(t.name) // 'undefined' `

7、作用域
全局; 函数作用域; 块级作用域;

8、原型
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c5f2308aa8424e7d9bcb5dfc9fe8c58c~tplv-k3u1fbpfcp-zoom-1.image?imageslim)

上图总结出:
1、所有对象都有一个属性 __proto__ 指向一个对象,也就是原型; 2、每个对象的原型都可以通过constructor找到构造函数,构造函数也可以通过prototype找到原型; 3、所有函数都可以通过__proto__找到Function对象 4、所有对象都可以通过__proto__找到Object对象; 5、对象之间通过__proto__连接起来,这样称之为原型链。当前对象上不存在的属性可以通过原型链一层层网上查找,直到顶层Object对象,再往上就是null

9、继承
class不是其他语言里的类,本质就是一个函数;
class Person {} Person instanceof Function //true

ES5和ES6继承的区别:
1、ES6继承的子类需要调用super()才能拿到子类,ES5的话是通过apply这种绑定的方式 2、类声明不会提升,和let这些一致。

ES5实现继承的方式有很多种:
function Super() {} Super.prototype.getNumber = function() { return 1; } function Sub() {} Sub.prototype = Object.create(Super.prototype, { constructor: { value:Sub, a: 2, } }) let s = new Sub(); s.getNumber();

10、深浅拷贝
浅拷贝:两个对象第一层的引用不相同就是浅拷贝 可以通过assign、扩展运算符等方式来实现浅拷贝:``` let a = { age: 1 } let b = Object.assign({}, a); a.age = 2; console.log(b.age) //1 b = {...a} a.age = 3 console.log(b.age) //2 ``` 深拷贝:两个对象内部所有的引用都不相同 最简单的深拷贝就是使用 JSON.parse(JSON.stringify(object)),不过只支持JSON类型,也不能处理循环引用的问题

11、Promise
1、使用all实现并行需求 2、Promise.all错误处理 3、手写all的实现 ``` function promiseAll(promises) { return new Promise( (resolve, reject) => { if (!Array.isArray(promises)) { return reject('arguments must be Array') } })let count = 0, newValues = new Array(promises.length); for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]).then( res => { count++; newValues[i] = res; if (count === newValues.length) { return resolve(newValues) } }, error => reject(error)) } } ```4、Promise.race() 比比谁最快,总是输出执行最快的 ``` Promise.race([ new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000) }), new Promise(function(resolve, reject) { setTimeout(() => resolve(2), 100) }), new Promise(function(resolve, reject) { setTimeout(() => resolve(3), 10) }) ]).then(value => { console.log(value) // 3 }) ```

12、async、 await
和promise相比,优势在于处理then的调用链,能够更清晰准确的写出代码。缺点在于滥用会导致性能问题,因为await会阻塞代码,如果之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去并发性,此时更应该使用Promise.all

var a = 0 var b = async () => { a = a + await 10 console.log('step2', a) // -> 10 } b() a++ console.log('step1', a) // -> 1

13、事件循环
记住:js是一门单线程语言,永远只能同时执行一个任务,js中的异步就是延迟执行的同步代码。 Event Loop执行顺序如下: 1、执行同步任务 2、执行完所有同步代码以后且执行栈为空,判断是否有微任务需要执行; 3、执行完所有微任务且微任务队列为空 4、是否有必要渲染页面 5、执行一个宏任务

console.log("script start") setTimeout( () => { console.log(setTimeout) }, 0) Promise.resolve().then( () => { Promise.resolve().then( () => { console.log('queueMicrotask') }) console.log("promise") }) console.log("script end")

上述代码执行过程描述:
1、遇到console.log执行并打印; 2、遇到setTimeout将回调加入到宏任务 3、遇到Promise.resolve(),此时状态改变,因此then回调加入到微任务队列; 4、遇到console.log执行并打印 此时同步任务全部执行完毕,分别打印了'script start'和'script end'。开始判是否有微任务需要执行 5、微任务队列存在任务。开始执行.then()回调函数 6、遇到queueMicrotask,将回调加入到微任务队列 7、遇到console.log打印 8、检查发现微任务队列存在任务,执行queueMicrotask回调 9、遇到console.log打印 此时发现微任务队列已经清空,判断是否需要进行UI渲染 10、执行宏任务,开始执行setTimeout回调 11、遇到console.log执行并打印执行一个宏任务即结束,寻找是否存在微任务,开始循环判断

14、模块化
CommonJs

module.exports = { a: 1 } exports.a = 1; 基本实现 var module = { exports: {} } var exports = module.exports var load = function(module) { var a = 1; module.exports = a; return module.exports; }

CommonJs和ESM的区别是:
1、前者支持动态导入,也就是require('xx.js'),后者使用import(); 2、前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大,而后者用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响 3、前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会变,所以如果想更新值,必须重新导入一次。后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化。

15、垃圾回收
16、手写考点
1、0.1 + 0.2 ! == 0.3;
因为JS采用IEEE 754双精度(64位),并且只要采用IEEE 754的语言都有该问题。 存在问题的原因是浮点数用二级制表示的时候是无穷的,因为精度的问题,两个浮点数相加会造成阶段丢失精度,因此再转换为十进制就出了问题。 解决方法如下: export const addNum = (num1, num2) => { let sq1, sq2, m; try { sq1 = num1.toString().split('.')[1].length; } catch(e) { sq1 = 0 } try { sq2 = num2.toString().split('.')[1].length; } catch(e) { sq2 = 0 } m = Math.pow(10, Math.max(sq1, sq2)); return (Math.round(num1 * m) + Math.round(num2 * m)) / m }

【js基础知识小结】2、防抖
简易版: const debounce = (func, wait = 200) => { let timer = 0; return function() { if (timer) clearTimeout(timer); timer = setTimeout( () => { func.apply(this, args) }, wait) } }

    推荐阅读