原型链
什么是原型?
任何对象实例都有一个原型,也叫原型对象,这个原型对象由对象的 内置属性_proto_指向它的构造函数的 prototype 指向的对象,即任何对 象都是由一个构造函数创建的,但是不是每一个对象都有 prototype, 只有方法才有 prototype。
什么是原型链?
原型链基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。我们知道,每个构造函数都有一个原型对象,每个原型对象都 有一个指向构造函数的指针,而实例又包含一个指向原型对象的内部指 针。
原型链的核心就是依赖对象的_proto_的指向,当自身不存在的属性 时,就一层层的扒出创建对象的构造函数,直至到 Object 时,就没有 _proto_指向了。因为_proto_实质找的是 prototype,所以我们只要找这个链条上的构造 函数的 prototype。
其中 Object.prototype 是没有_proto_属性的,它 ==null。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的 指针,而实例都包含指向原型对象内部的指针。
我们让原型对象(1)等 于另一个原型对象的实例(2), 此时原型对象(2)将包含一个指向原型对象(1)的指针, 再让原型对象(2)的实例等于原型对象(3),如此层层递进就构成了实 例和原型的链条,这就是原型链的概念 每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数想 指针(constructor),而实例对象都包含一个指向原型对象的内部指针 (__proto__)。如果让原型对象等于另一个原型对象的实例,此时的原型 对象将包含一个指向另一个原型的指针(__proto__),另一个原型也包含 着一个指向另一个构造函数的指针(constructor)。假如另一个原型又是 另一个类型的实例……这就构成了实例与原型的链条。也叫原型链
原型继承?
原型继承是 js 的一种继承方式,原型链作为实现继承的主要方法,其基本 思路是利用原型让一个引用类型继承另一个引用类型的属性和方法。原型继承:利用原型中的成员可以被和其相关的对象共享这一特性,可 以实现继承,这种实现继承的方式,就叫做原型继承。
继承
继承有六种
原型继承
原型继承是 js 的一种继承方式,原型链作为实现继承的主要方法,其基本 思路是利用原型让一个引用类型继承另一个引用类型的属性和方法, 原型继承:利用原型中的成员可以被和其相关的对象共享这一特性,可 以实现继承,这种实现继承的方式,就叫做原型继承
特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
- 无法实现多继承
- 来自原型对象的所有属性被所有实例共享(来自原型对象的引用属性是所有实例共享的)
- 创建子类实例时,无法向父类构造函数传参
借用构造函数继承
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
特点:
- 解决了1中,子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
特点:
- 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
原型式继承
寄生式继承
寄生组合式继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
闭包
什么是闭包?
闭包可以简单理解成:定义在一个函数内部的函数。其中一个内部函数 在包含它们的外部函数之外被调用时,就会形成闭包。
闭包的特点?
特点:
1.函数嵌套函数。
2.函数内部可以引用外部的参数和变量。
3.参数和变量不会被垃圾回收机制回收
使用:
1.读取函数内部的变量;
2.这些变量的值始终保持在内存中,不会在外层函数调用后被自动清除
优点:
1:变量长期驻扎在内存中;
2:避免全局变量的污染;
3:私有成员的存在 ;
缺点:
会造成内存泄露
JS 的闭包
闭包是指有权访问另外一个函数作用域中的变量的函数。
闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继 续存在。闭包就是就是函数的“堆栈”在函数返回后并不释放,我们 也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配。
当在一 个函数内定义另外一个函数就会产生闭包。
为什么要用?
匿名自执行函数:我们知道所有的变量,如果不加上 var 关键字,则 默认的会添加到全局对象的属性上去,
这样的临时变量加入全局对象 有很多坏处,比如:别的函数可能误用这些变量;
造成全局对象过于 庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。
除 了每次使用变量都是用 var 关键字外,我们在实际情况下经常遇到 这样一种情况,即有的函数只需要执行一次,其内部变量无需维护, 可以用闭包。
es6
Let 与 var 与 const 的区别?
Var 声明的变量会挂载在 window 上,而 let 和 const 声明的变量不会
Var 声明的变量存在变量提升,let 和 const 不存在变量提升 同一作用域下 var 可以声明同名变量,let 和 const、不可以 Let 和 const 声明会形成块级作用域
Let 暂存死区
Const 一旦声明必须赋值,不能用 null 占位,声明后不能再修改,如果 声明的是复合类型数据,可以修改属性,简单理解就是:
const定义的常量是基本数据类型的时候不可以被更改
const定义的常量是引用数据类型的时候,其值可以被更改。
数组方法有哪些?
push() 从后面添加元素,返回值为添加完后的数组的长度
arr.pop() 从后面删除元素,只能是一个,返回值是删除的元素
arr.shift() 从前面删除元素,只能删除一个 返回值是删除的元素
arr.unshift() 从前面添加元素, 返回值是添加完后的数组的长度
arr.splice(i,n) 删除从 i(索引值)开始之后的那个元素。返回值是删除的元 素
arr.concat() 连接两个数组 返回值为连接后的新数组
str.split() 将字符串转化为数组
arr.sort() 将数组进行排序,返回值是排好的数组,默认是按照最左边的数 字进行排序,不是按照数字大小排序的
arr.reverse() 将数组反转,返回值是反转后的数组
arr.slice(start,end) 切去索引值 start 到索引值 end 的数组,不包含 end 索引的值,返回值是切出来的数组
arr.forEach(callback) 遍历数组,无 return 即使有 return,也不会返回 任何值,并且会影响原来的数组
arr.map(callback) 映射数组(遍历数组),有 return 返回一个新数组 。
arr.filter(callback) 过滤数组,返回一个满足要求的数组
普通函数和构造函数的区别?
1.构造函数也是一个普通函数,创建方式和普通函数一样,但是构造函数 习惯上首字母大写
2.调用方式不一样,普通函数直接调用,构造函数要用关键字 new 来调 用
3.调用时,构造函数内部会创建一个新对象,就是实例,普通函数不会创 建新对象
4.构造函数内部的 this 指向实例,普通函数内部的 this 指向调用函数的 对象(如果没有对象调用,默认为 window)
5.构造函数默认的返回值是创建的对象(也就是实例),普通函数的返回 值由 return 语句决定6.构造函数的函数名与类名相同
Promise 的理解?
promise 有三种状态:
pending 初始状态也叫等待状态,fulfiled 成功状态,rejected 失败状态;状态一旦改变,就不会再变
Promise 的两个特点:
1、Promise 对象的状态不受外界影响
2、Promise 的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,
Promise 的三个缺点:
1)无法取消 Promise,一旦新建它就会立即执行,无法中途取消
2)如果不设置回调函数,Promise 内部抛出的错误,不会反映到外部
3)当处于 pending(等待)状态时,无法得知目前进展到哪一个阶段, 是刚刚开始还是即将完成
async 的用法?
Async 就是 generation 和 promise 的语法糖,async 就是将 generator 的*换成 async,将 yiled 换成 await 函数前必须加一个 async,
异步操作方法前加一个 await 关键字,意思就 是等一下,执行完了再继续走,
注意:await 只能在 async 函数中运行, 否则会报错 Promise 如果返回的是一个错误的结果,如果没有做异常处理,就会报 错,所以用 try..catch 捕获一下异常就可以了
this指向
this指向是什么?
this 通常指向的是我们正在执行的函数本身,或者是, 指向该函数所属的对象。
全局的 this → 指向的是 Window
对象中的 this → 指向其本身
事件中 this → 指向事件对象
改变函数内部 this 指针的指向函数(bind,apply,call 的区 别)
通过 apply 和 call 改变函数的 this 指向。
他们两个函数的第一个 参数都是一样的表示要改变指向的那个对象,第二个参数,apply 是 数组,而 call 则是 arg1,arg2...这种形式。
通过 bind 改变 this 作 用域会返回一个新的函数,这个函数不会马上执行
实现一个 bind 函数
原理:通过 apply 或者 call 方法 来实现。
Function.prototype.bind=function(obj,arg){ var arg = Array.prototype.slice.call(arguments,1); var context = this; var bound=function(newArg){ arg=arg.co ncat(Array.prototype.slice.call(newArg) ); return context.apply(obj,arg); } var F=function(){} // 这里需要一个寄生组 合继承 F.prototype=context.prototype; bound.prototype=new F(); return bound; }
事件循环
JavaScript 是一门单线程语言,异步操作都是放到事件循环队列里面,等待主执行栈来执行的,并没有专门的异步执行线程。同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。
关键的步骤
1.在此次 tick 中选择最先进入队列的任务( oldest task ),如果有则执行(一次)
2.检查是否存在 Microtasks ,如果存在则不停地执行,直至清空Microtask Queue
3.更新 render
宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)microtask 优先于 task 执行