let const
var
在let和const出来之前,函数作用域分为全局作用域跟函数作用域。用var先声明,不赋值,会造成一个变量提升的问题。比如说我用一个if循环包裹住一段函数,里面用var定义了一个参数,其实这个var不会形成一个作用域,这个作用域是全局的。 var的特点如下:
- var 在全局作用域定义时,还会挂载到window(浏览器环境)上,生成全局变量
- 可以重复声明,后面申明覆盖前面声明
- 存在变量提升(自动把声明提到作用域顶部),不规范写法,易导致可读性差
let
- 除了全局作用域和函数作用域外,还新增了块级作用域( { } )
- let和const都不能重复声明
- 作用域内,声明语句前使用会报错(暂时性死区 temporal dead zone TDZ)
const
- let 基础上,规定了申明后不可修改,用于声明常量
- 声明后不可修改,必须声明时候赋值
- 当使用const声明一个对象和数组时后,此变量的引用便不可修改,但是此变量内部属性还是可以进行修改的。
Set
Set 只有键值,没有键名,类似于一个数组,但数组可以允许元素重复,Set 不允许元素重复,用这个可以解决数组去重,求并集,求交集等问题。
数组去重与字符串去重
// Set可以用于字符串或者数组的去重
let arr = [3, 4, 3, 5, 6, 5, 6]
let str = "23234545"
let uniqueArr = new Set([...arr])
console.log(uniqueArr)
let uniqueStr=new Set([...str].join(""))
console.log(uniqueStr)
求并集
let a=[1,2,3]
let b=[2,3,4]
let union=new Set([...a,...b])
console.log(union)
求交集
let bb=new Set(b)
let intersect=new Set([...a].filter(e=>
bb.has(e)
))
console.log(intersect)
Map
Map
Map主要就是键值对的存储,类似于对象,但是对象的键名只能是字符串或者是数组,键名可以是任意类型的值,相同键名只能出现一次。
- 一个键名只能出现一次;
- 有set、get、has、delete、clear等增删改查方法,有size属性;
- 与Set系列不同,它有keys、values、entries等方法;
WeakMap
WeakMap :WeakMap 是JavaScript的一个内置对象,是一种数据结构。WeakMap对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
-
因为 WeakMap 实例不会妨碍垃圾回收,所以非常适合保存关联元数据(比如深拷贝解决循环引用问题)。
-
WeakMap 跟Map相似,也是生成键值对的集合,但是键名只能是对象,null除外。
-
WeakMap不可枚举!没有遍历操作,也就是
keys() ,values(), entries()也没有size -
只有四个方法,get set has delete
WeakSet
WeakSet 中的对象都是弱引用:即垃圾回收机制不考虑WeakSet对其内部对象的引用,也就是说,在WeakSet中的对象当没有外界其他对象引用它时,会被垃圾回收机制回收,并不考虑WeakSet中还有它。实际用处的话,就比如我们要在一个设置一个按钮的禁用列表,我们可以用weakSet把他们存起来,通过has查询一下是否被禁用了,如果我们用普通的数组存储他们,会造成一定的性能损耗,不利于垃圾回收机制。那这个时候就可以用WeakSet存储要被禁用的Dom节点,不必担心后续这个Dom节点从文档中被移除后造成的内存泄露,WeakSet是弱引用的,经典用法就是存放Dom节点。
-
WeakSet 的成员只能是对象,不能是其他类型的值, WeakSet 里面的引用,都不计入垃圾回收机制, WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息
-
不可遍历
-
有三个方法:
add, has, delete -
没有size属性,没办法遍历他的成员
-
WeakSet 的一个作用是存储DOM节点,不用担心这些节点从文档移除时候,会引发内存泄漏
类的继承extends
js的继承
原型继承
原型链继承的特点是对象实例可以共享所有继承的属性和方法。但是父类的引用类型数据会被子类共享,因为两者指向同一个内存空间,且子类实例不能给父类构造函数传参。比如说我new两个实例用原型继承Person1,这两个实例的引用类型是共享的。
function Person1() {
this.name = "小名";
this.eats = ["苹果"]
this.getName = function () {
console.log(this.name);
}
}
Person1.prototype.get = () => {
console.log("Person1.prototype上的方法")
}
function student2() {}
student2.prototype = new Person1()
const stu2 = new student2();
stu2.name = "消化";
stu2.eats.push("香蕉");
console.log(stu2.name)
console.log(stu2.eats);
// stu2.getName();
stu2.get();
const per1=new Person1()
per1.getName()
console.log(per1.eats)
构造函数继承
构造函数配合call继承,call改变指针方向,子类不能继承父类原型(prototype)上的方法
function Person3() {
this.name = "小名";
this.eats = ["苹果"]
this.getName = function () {
console.log(this.name);
}
}
Person3.prototype.get = () => {
console.log("Person3.prototype上的方法")
}
function Student3(){
Person3.call(this)
}
const stu3=new Student3();
stu3.name="小构造函数继承"
stu3.eats.push("构造函数")
console.log(stu3.name)
console.log(stu3.eats);
const per3=new Person3()
per3.getName()
组合继承
组合继承,用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
function Person4() {
this.name = "小名";
this.eats = ["苹果"]
this.getName = function () {
console.log(this.name);
}
}
Person4.prototype.get = () => {
console.log("Person4.prototype上的方法")
}
function Student4(){
Person4.call(this)
}
Student4.prototype=new Person4()
const stu4=new Student4();
console.log(stu4)
stu4.name="组合继承"
stu4.eats.push("组合函数")
console.log(stu4.name)
console.log(stu4.eats);
stu4.getName()
stu4.get()
寄生组合继承
寄生组合继承,通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。其实就是设置了一个fn来中转一下原型上的继承。
function Person5() {
this.name = "小名";
this.eats = ["苹果"]
this.getName = function () {
console.log(this.name);
}
}
Person5.prototype.get = () => {
console.log("Person5.prototype上的方法")
}
function Student5(){
Person5.call(this)
};
function fn(){}
fn.prototype=new Person5()
Student5.prototype=new fn()
const stu5=new Student5()
console.log(stu5)
stu5.name="消化";
stu5.eats.push("香蕉");
console.log(stu5.name)
console.log(stu5.eats);
stu5.getName();
stu5.get()
extends继承
个人比较喜欢这种,简单透彻,像c++语法。
class parent {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class child extends parent {
constructor(name, age, color) {
super(name, age);
this.color = color;
}
}
Promise
个人总结:promise是异步编程的一种解决方案,promise出现就是为了解决回调地狱的问题,开一个构造器excutor,传入resolve与reject,要注意promise是同步的,立即执行的。三种状态在代码中可以体现。主要用处就是axios是基于promise封装的,我们在axios响应拦截器那里获取到数据之后可以用.then接受成功后的回调。.then的话是异步的,处于微任务队列,他一定要等到promise返回改变后的状态才会执行,这里就需要一个#run的数组来保存上一个promise的信息,等到他的状态改变之后才会执行.then里的回调,又因为.then支持链式调用,所以.then包的一定会返回一个promise。.then会接受两个参数,onFullfilled与onRejected,在他们内部会判断一下他们的状态如果是pending,把他们放进onFullfilledCallback与onRejectedCallback。那么这儿会有一个问题, .catch和.then是同级的吗?
- Promise.prototype.catch()方法 Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数,.catch其实就是.then的第二个参数
那么还会涉及到promise的三个常用api:all,race,allsettled。
- all的话是将一堆promise实例放在一个数组里,用all调用,成功后返回一个promise,如果这个过程有一个失败,就立即返回这个失败的原因,后续不会再执行了。
- race的话是返回第一个响应的promise,无论成功还是失败。
- allsettled的 执行结果不会失败,返回一个数组,分别对应参数数组中各个Promise实例的状态。
简单promise手写
function MyPromise(excuor){
let self=this;
self.status="pending";
// 成功的原因
self.value=null;
// 失败的理由
self.reason=null;
self.onFullfilledCallback=[];
self.onRejectedCallback=[];
function resolve(value){
if(self.status='pending'){
self.value=value;
self.status='fullfilled';
self.onFullfilledCallback.forEach(item=>item(value))
}
}
function reject(reason){
if(self.status='pending'){
self.reason=reason;
self.status='rejected';
self.onFullfilledCallback.forEach(item=>item(reason))
}
}
try{
excuor(resolve,reject)
}
catch(err){
reject(err)
}
}
MyPromise.prototype.then=function(onFullfilled,onRejected){
// 容错处理
onFullfilled=typeof onFullfilled==='function'?onFullfilled:function(data){resolve(data)}
onRejected=typeof onRejected==='function'?onRejected:function(err){throw(err)}
// 支持异步处理
let self=this;
// debugger
if(self.status='pending'){
self.onFullfilledCallback.push(onFullfilled)
self.onRejectedCallback.push(onRejected)
}
}
let demo=new MyPromise((resolve,reject)=>{
console.log("我是涵大帅")
setTimeout(()=>{
console.log("1222")
resolve(1)
},1000)
})
Async Await
个人总结:async,await是让异步操作变得更加简便,一般用async是把他放在一个函数之前,这个被放置了async的函数会返回一个promise的回调,await则是等待这个promise对象的状态变为fullfilled,这个await后面跟的是异步的操作。一般来说await后面是跟着promise的,如果是promise的话,是会阻塞后续代码执行的,但是如果是函数或者是其他,就不会等待这个await。下面有道面试题可以看看。如果async包的那个函数抛出错误了怎么办呢?可以加个try和catch的代码块捕捉成功和错误。这部分参考了这位作者juejin.cn/post/712165…
(async function () {
try {
const a = await new Promise((resolve, reject) => reject(123))
console.log("我成功了!")
} catch (err) {
console.log('我出错了,但我不想管!', err);
}
console.log(456);
})()
面试题
function log(time) {
setTimeout(function () {
console.log(time);
return 1;
}, time)
}
async function funccc() {
let a = await log(1000);
let b = await log(3000);
let c = log(2000);
console.log(a);
console.log(1)
}
funccc();
这个题里面的await就是来误导的,在await后面不是一个promise的时候,是不会去等待其执行完成的. 所以其实这个问题就很简单,前面介个setTimeout存入队列,先执行,a,1,这个时候,a已经定义,但是没有初始值,所以就是undefined,执行完后,再执行队列中的值,也是不会相互等待哦,依次执行下来,就是1000,2000,3000。
async/await实际上是对Generator(生成器)的封装,是一个语法糖。ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,通过next()方法可以切换到下一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方案。
function* generate() {
const res = yield request('1111');
const res2 = yield request(res);
console.log(res2)
}
function run() {
const g = generate(); //拿到句柄
function exec(params) {
const {
value,
done
} = g.next(params);
if (!done) {
value.then(res => exec(res))
}
}
exec()
}
run()
扩展运算符[...arg]
扩展运算符格式很简单,就是三个点(...)
- 链接:juejin.cn/post/684490…
扩展运算符可以使用的点有很多,比如数组的浅拷贝,数组拼接,字符串转数组...
箭头函数
普通this指向
this的绑定有四种:默认绑定;显示绑定;隐式绑定;new绑定;
默认绑定
-
绑定全局的window
`console.log(this === window); //true` -
全局作用域下独立调用函数,this指向window
function test() { console.log(this === window); //true } test();
隐式绑定
按照我的理解是结合上下文的绑定,跟它所处的作用域是无关的,从第二段代码可以看出,谁调用他就指向谁
let obj = {
name: 'obj',
foo: function () {
console.log(this); //this指向obj
}
}
obj.foo()
}}
改一下
let obj = {
name: 'obj',
foo: function () {
console.log(this); //obj
function test() {
console.log(this); //window 为什么? 因为test独立调用
}
test()
}
}
obj.foo()
隐式丢失
let obj = {
name: 'obj',
foo: function () {
console.log(this); //window 为什么不是obj? bar拿到obj.foo的引用,然后在全局下独立调用
}
}
let bar = obj.foo
bar()
函数作为参数
function foo() {
console.log(this); //window obj.foo在bar函数内独立调用
}
function bar(fn) {
fn()
}
let obj = {
name: 'obj',
foo: foo
}
bar(obj.foo)
函数作为参数时,参数函数叫做子函数,外面的函数叫父函数,子函数也叫回调函数,像这样的函数有很多,
比如forEach 、setimeout,这些函数里的参数函数也叫内置参数
记住:父函数有能力决定子函数this的指向,例如forEach里第一个参数是一个函数,第二个参数就是this绑定的对象,不写默认绑定window
显式绑定
call、apply、bind bind返回一个新函数,新函数指向绑定的对象,旧函数不会
new绑定
this指向函数实例化之后的对象
箭头函数指向
-
普通函数可以用于构造函数(
new)。箭头函数不可用于构造函数,会报错。 -
箭头函数没有自己的
this,是当前上下文环境的this,不会被call()或apply()或bind()修改this指向。 -
由于 箭头函数没有自己的this指针,通过 call()或apply()方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。
-
每一个普通函数调用后都具有一个arguments对象,用来存储实际传递的参数。但是箭头函数并没有此对象。
new一个实例对象发生了啥
function newOperator(ctor, ...args) {
// 创建一个新对象,并将该对象的[[prototype]]指向构造函数的prototype
const obj = Object.create(ctor.prototype);
// 绑定this到新对象中
const res = ctor.apply(obj, args);
// 判断构造函数返回的是否是一个对象,是则返回,否则返回新创建的对象
const resType = typeof res;
return (resType === 'object' && resType !== null) || resType === 'function'
? res
: obj;
}
function Test() {
this.a = 1;
this.b = 2;
}
console.log(newOperator(Test)); // Test {a: 1, b: 2}
解构赋值
对应使用就可以了
详细可以看链接
链接:juejin.cn/post/684490…
但是这两天看到一道面试题,小恶心
`
const { a , b:y } = { a:3 , b:4 }
console.log(a)
console.log(y)
console.log(b)
`
对应输出什么,答案是y是4,a是3,b会报错,涉及到别名的使用吧。