目录
4.原型式继承 (也是Object.create()的手写,本质是一个浅拷贝)
三:回调函数:函数作为参数传递给另外一个函数。例如定时器、事件、ajax、生命周期回调函数
microtask微任务:更新应用程序状态的任务,promise.then及回调,MutationObserver,process.nextTick,Object.observe
六:Web Worker(模拟多线程) H5提供了js多进程的实现 _____浏览器输出报错。。。了解即可
* Worker.prototype.postMessage(): 主进程和子线程相互发送消息
一:继承(原型继承、构造函数继承、组合继承)
- 面向对象语言三大特征:封装、继承、多态。对比我们从前学过的java语言,他有类Class,但是我们的js(es5之前)没有,(es5)之前我们可以将构造函数理解为类,但是在es6当中,出现了新的关键字 class类 但是他也不是真正的类,底层依旧是通过封装构造函数来实现的,其本质还是一个语法糖
- 继承:核心问题是让 子类函数 能够继承 父类函数已有的属性和方法
关于父类、子类、及属性:
类:Dog (父类)
品种:柯基、哈士奇等等 (子类)
属性:名字、颜色、年龄
1.构造函数继承
父.call(子,形参1,形参2...)
父.apply(子,arguments) apply第二个参数必须为数组,所以我们可以用arguments
在子类的内部通过call()或者apply()改变父类中this的指向变成子类,并且传递参数
①优点
- 没有引用值共享问题
②缺点:
- 子类的prototype不指向父类,无法访问父类prototype上的属性方法
<script>
// 父类方法
function Dog(name,age){
this.name = name
this.age = age
this.x = [1,2,3,4]
this.eat=function(){
console.log('我在吃饭')
}
}
// 父类原型链上添加方法
Dog.prototype.play = function(){
console.log('我叫:'+this.name+'我喜欢玩游戏')
}
// 子类方法
function keji(name,age,hobby){
Dog.call(this,name,age)
// 或者用apply,,后面的形参是一个伪数组arguments
// Dog.apply(this, arguments);
this.hobby = hobby
}
var father = new Dog('父',10) //父类实例化对象
var child = new keji('子',2,'eat') //子类实例化对象
console.log(father)
console.log(child)
// child实例不是父类Dog的实例,只是子类keji的实例(所以不能访问Dog.prototype上定义的方法)
console.log(father instanceof Dog) // true
console.log(child instanceof Dog) // false
/* 可以方便的继承父类型的属性,但是无法继承原型中的方法(即不能访问Dog.prototype上定义的方法)
,因此所有方法属性都写在构造函数中,每次创建实例都会初始化 */
father.eat() // 我在吃饭
child.eat() // 我在吃饭
// 测试 1. 缺点: 子类不能调用父类原型上的方法
father.play() // 我叫:父我喜欢玩游戏
child.play() // TypeError: child.play is not a function
// 测试 2. 优点: 子类们没有引用值共享问题
var child2 = new keji('子',2,'eat') //子类实例化对象
child.x.push(8)
console.log(child.x)
console.log(child2.x)
</script>
2.原型链式继承
子类的原型等于父类的实例化对象
①优点
- 子类可以访问父类原型上的方法
②缺点
- 有引用值共享的问题,一个子类修改引用值,所有子类都跟着修改了
- 创建子类型的时候,不能向父类型的构造函数中传递参数。
<script>
// 父类方法
function Dog(name){
this.name = '父类'
this.x = [1,2,3,4]
}
// 父类原型链上添加方法
Dog.prototype.play = function(){
console.log('我叫:'+this.name+'我喜欢玩游戏')
}
// 子类方法
function keji(name,age){
this.name = '子类'
}
// 子类的原型等于父类的实例化对象
keji.prototype = new Dog()
keji.prototype.constructor = keji; //手动的将keji原型上的构造器属性重新指向keji
var father = new Dog()
var child1 = new keji()
// 测试 1: 优点 :能访问父类原型上的方法
child1.play() //我叫:子类我喜欢玩游戏
father.play() //我叫:父类我喜欢玩游戏
// 测试 2: 缺点 :有地址值共享的问题
var child2 = new keji()
child1.x[0] = 5
console.log(child1.x) // [5, 2, 3, 4]
console.log(child2.x) // [5, 2, 3, 4]
// 测试 3: 修改构造器指向
// 正常的 实例化对象x的构造器constructor 指向 fn()
function fn(){
this.s = 5
}
let x = new fn()
console.log(x) // 正常的输出指向x
//继承后的keji构造函数 丢失constructor构造器,我们需要手动将keji原型上的构造器属性重新指向keji
console.log(child1) // 输出可以看到 构造器指向指回了 keji
</script>
3.组合继承(构造函数继承属性、原型继承方法 一起使用)
在子类的内部调用父类,通过call()或者apply()改变父类中this的指向变成子类,并且传递参数
子类的原型等于父类的实例化对象
①优点
-
可以访问父类原型中的方法
-
引用类型不共享地址值
②缺点
- 父类构造函数被重复调用两次(第一次是子类原型指向父类实例是,第二次是子类方法内部用.call的时候),子类原型中会存在两份相同的属性/方法。
<script>
// 父类构造函数
function Dog(name,age){
this.name = name
this.age = age
this.x = [1,2,3,4]
this.eat = function(){
console.log('我叫:'+this.name+'我爱吃')
}
}
// 父类的原型中添加方法
Dog.prototype.play = function(){
console.log('我叫:'+this.name+'我喜欢玩')
}
// 子类构造函数
function keji(name,age){
this.name = name
this.age = age
Dog.apply(this,arguments) //测试 3: 缺点 第二次执行
}
// 修改子类原型指向父类实例化对象
keji.prototype = new Dog() //测试 3: 缺点 第一次执行
keji.prototype.constructor = keji;
let father = new Dog('父类',18)
let child = new keji('子类',12)
// 测试 1: 优点:子类可以访问父类原型上的方法
father.play() //我叫:父类我喜欢玩
child.play() //我叫:子类我喜欢玩
// 测试 2: 优点: 子类地址值不共享
let child2 = new keji('子类2',16)
child2.x[0] = 5
console.log(child.x) //[1, 2, 3, 4]
console.log(child2.x) //[5, 2, 3, 4]
</script>
4.原型式继承 (也是Object.create()的手写,本质是一个浅拷贝)
父类构造函数里面创建子类的构造函数,将子类的原型指向父类,return 回 子类的实例化对象
①优点:子类能访问父类原型上的方法
②缺点: 子类之间引用地址值共享
<script>
// ① 创建一个object方法并且传参一个父类对象,
function object(Dog){
// ② object方法里面有创建一个子类的构造函数,
function keji(){}
// ③ 将子类构造函数的原型指向被传参的父类对象,
keji.prototype = Dog // 子类原型指向父类构造函数
// ④ return 子类构造函数的实例化对象
return new keji()
}
var father = {
name:'父类',
x:[1,2,3,4,5],
}
// 原型上添加一个方法
father.__proto__.say = function(){
console.log('hi')
}
// 测试 1: 缺点:子类之间存在引用地址值共享问题
var child1 = object(father) //child 是 new keji 的实例化对象
var child2 = object(father)
child1.x[0] = 5
console.log(child1.x) //[5, 2, 3, 4, 5]
console.log(child2.x) //[5, 2, 3, 4, 5]
console.log(child2.s) //[5, 2, 3, 4, 5]
// 测试 2: 优点: 子类能访问父类原型上的方法
father.say() //hi
child1.say() //hi
child2.say() //hi
</script>
5.寄生式继承
就是给原型式继承外面套个壳子 给实例化对象统一添加属性方法,然后再赋值给子实例化对象们
①优点:子类能访问父类原型上的方法(同原型式继承一样)
②缺点: 子类之间引用地址值共享(同原型式继承一样)
<script>
function object(Dog){
function keji(){}
keji.prototype = Dog
return new keji()
}
// 给总实例化对象内部添加属性方法
function out(father){
var childs = object(father) // childs 为 keji 的实例化对象
childs.sayHi = function(){ // 统一给实例化对象上面添加sayHi方法
console.log('hi')
}
return childs //返回 添加了新属性方法的 childs 实例化对象
}
var father = {
name:'父类',
x:[1,2,3,4,5],
eat:function(){
console.log('我在吃')
}
}
// 子实例化对象们
var child1 = out(father)
var child2 = out(father)
// 测试 1: 缺点:有子类引用地址值共享
child1.x[0] = 5
console.log(child1.x) // [5, 2, 3, 4, 5]
console.log(child2.x) // [5, 2, 3, 4, 5]
// 测试 2: 优点:子类能否访问父类原型上的方法
child1.eat() // 我在吃
child2.eat() // 我在吃
</script>
6.寄生组合式继承
结合借用构造函数继承(没有引用地址值共享问题)再修改子类原型指向父类实现继承,
①优点
- 修复了组合继承构造函调用两次的缺点
- 借用了构造函数继承,在子类构造函数的内部借用.call将父类的this指向修改为子类
- 修改了子类构造函数的原型→子类可以访问父类原型上的方法
<script>
// 父类构造函数的方法
function Dog(name){
this.name = name,
this.x = [1,2,3,4,5]
}
// 父类原型上添加方法
Dog.prototype.sayHi = function(){
console.log('hi')
}
// 子类构造函数 (解决①子类引用地址值共享问题)
function keji(name){
this.name = name
Dog.call(this,name) //在子类构造函数内部,将父类的this指向修改为子类
}
// 将子类构造函数原型指向父类构造函数原型 (解决②子类无法访问父类原型上的方法问题)
function revisePrototype(keji,Dog){
// 将子类原型指向父类原型
Object.setPrototypeOf(keji.prototype,Dog.prototype)
// 将子类的constructor构造器指回自己
keji.constructor = keji;
}
// 执行
revisePrototype(keji,Dog)
// 测试 优点:1.子类可以访问父类原型上的方法
let child = new keji()
child.sayHi()
// 测试 优先:2.子类之间不存在引用地址值共享问题
let child2 = new keji()
child.x[0] = 5
console.log(child.x) //[5, 2, 3, 4, 5]
console.log(child2.x) // [1, 2, 3, 4, 5]
</script>
7.ES6的extends继承(记得要写super关键字)
优点
- 可以访问父类构造函数上的方法
- 子类实例化对象之间不存在引用地址值共享的问题
<script>
class Dog{
// 公共属性
constructor(name,age){
this.name = name
this.age = age
this.ceshi = '测试'
}
// 公共方法
eatF() {
console.log('我是父类:我爱吃')
}
// 私有,Dog类有的私有属性和私有方法,实例化对象不继承私有的
static s = '我是Dog父类私有的属性'
static play(){
console.log('我是Dog父类私有的方法')
}
}
// 给父类的原型上添加方法
Dog.prototype.sayHi = function(){
console.log('我是父类原型上的方法')
}
// 子类 extends 继承
class keji extends Dog{
// keji的公共属性
constructor(name,age){
// 初始化this,相当于Dog.call(this,name,age) ,必须先super,才能使用this关键字
super(name,age)
this.x = [1,2,3,4,5]
}
// keji的公共方法
eatZ() {
console.log('我是子类:我爱吃')
}
}
// 创建子类和父类的实例化对象
let father = new Dog()
var child1 = new keji('小杨1',18)
// 测试 1: 优点 可以访问父类构造函数上的方法
father.sayHi() // 我是父类原型上的方法
child1.sayHi() // 我是父类原型上的方法
// 测试 2: 优点 子类实例化对象之间不存在引用地址值共享的问题
var child2 = new keji('小杨2',100)
child1.x[0] = 5
console.log(child1.x) // [5, 2, 3, 4, 5]
console.log(child2.x) // [1, 2, 3, 4, 5]
</script>
二:进程和线程
1.进程是比如打开QQ,打开浏览器,这是两个进程
2.进程是由多个线程组成的,线程分为单线程和多线程
多线程:浏览器,可以同时运行多个页面
单线程:JS
JS:单线程的异步语言
- JS是单进程的,只能同时做一件事,遇到等待比如网络不好卡住了怎么办?异步就是解决单线程等待的问题
- 浏览器和nodejs已支持JS启动进程,如Web Worker
- JS和DOM渲染共用同一个线程,因为JS可修改DOM解构
- 异步是基于 callback回调函数 形式进行调用的
三:回调函数:函数作为参数传递给另外一个函数。例如定时器、事件、ajax、生命周期回调函数
你定义的,你没调用,但是最终执行了
四:同步异步:
事件绑定语句是同步,回调函数的触发是异步
定时器的启动是同步,定时器的触发是异步
同步代码会阻塞,前一个执行完才能执行下一个
1.同步:同步执行完成才会去执行异步,会阻塞代码执行
2.异步:只要是异步的任务都会有自己的管理模块进行托管,不会阻塞代码运行(回调函数都是异步)
五:事件循环(EventLoop)
解决javaScript
单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
同步》微任务》宏任务
1.事件队列(调用栈)执行同步代码(先)
基础console.log语句 和 new 执行的代码是同级的 无优先级
2.任务队列 执行异步代码(后)
①宏观任务 macrotask
②微观任务 microtask
任务优先级:process.nextTick>promise.then>setTimeout>setImmediate(后两个如果延迟时间一致,则out先执行,如果out有延迟时间,immediate没有,则immediate先执行)
macrotask宏任务:包含执行整体的js代码
script
,定时器(setTimeout
、setInterval
、setImmediate
),读取文件,IO操作,UI render,事件回调,XHR回调,microtask微任务:更新应用程序状态的任务,promise.then及回调,
MutationObserver
,process.nextTick
,Object.observe
3.练习
- 两个微任务同时存在时,不要一直沿着一个微任务深入思考,应该先让微任务1整体入队,微任务2整体入队,然后再将微任务1中的小微任务在微任务2后入队
注意: .then 执行的条件是:Promise的状态必须改变 也就是 resolve()
2,5,6,8,7,3,1,4
1,5,2,4,3
5,1,4,3,6,2
<script>
setTimeout(() => {
console.log('0');
},0)
new Promise((resolve,reject) => {
console.log('1');
resolve()
}).then(() => {
console.log('2');
new Promise((resolve,reject) => {
console.log('3');
resolve()
}).then(() => {
console.log('4');
}).then(() => {
console.log('5');
}).then(() => {
console.log('100');
})
}).then(() => {
console.log('6');
})
new Promise((resolve,reject) => {
console.log('7');
resolve()
}).then(() => {
console.log('8');
})
</script>
.then执行的前提是上一个函数有执行结果
六:Web Worker(模拟多线程) H5提供了js多进程的实现 _____浏览器输出报错。。。了解即可
1.自定义Worker.js文件内不执行输出语句
2.相关API
* Worker: 构造函数, 加载分线程执行的js文件
* Worker.prototype.postMessage(): 主进程和子线程相互发送消息
* Worker.prototype.onmessage = function(event){}: 用于接收另一个线程的回调函数;onmessage里面有事件对象event,event上面有一个属性叫做data,用来接收主线程传递过来的消息