面向对象及事件循环

一、面向对象

1.1 普通函数与构造函数

函数还是之前的函数,唯一的区别就是首字母大写

function Foo(m, n) {
  let ret = m + n
  this.m = m
  this.n = n
  return ret
}

// 01 普通函数调用
let ret = Foo(10, 20)
console.log(ret)

// 02 构造函数执行
let res = new Foo(20, 20)
console.log(res)

1.1.1 普通函数

  • 正常调用,不需要 new 关键字
  • 执行过程遵循堆栈 + 作用域及作用域链查找的运行机制

1.1.2 构造函数

  • 使用 new 关键字调用
  • 与普通函数类似,同样会创建私有上下文,然后进栈执行
  • 执行 new 操作时,浏览器会创建一个空间表示空以象与 this 关联
  • 函数体内如果没有 return 或者说 return 的是基本数据类型,默认返回对象实例
  • 函数体内如果返回引用类型,那么就以自己返回为主
  • 函数此时叫做类, 返回的结果叫做对象实例

1.1.3 new 操作符

  • 正常情况下使用 new 完成对象实例创建,如果当前类不需要传递参数,则可以不加括号运行
  • new Foo , 没有加小括号的时候说明 Foo 不需要传参,称之为无参列表
  • new Foo 与 new Foo() 的优先级不同,前者为 19, 后者为 20
  • 每一次 new 都会将函数重新执行,生成一个新的执行上下文,创建一个新的实例对象,因此两个实例对象不一样

1.2 原型及原型链

原理图

1.2.1 名词说明

  • prototype 属性
    • 每一个函数(除了箭头函数)数据类型, 都自带一个 prototype 属性,指向原型对象(Function 除外)
    • 每个原型对象自带一个 constructor 属性,指向当前构造函数本身
    • 函数数据类型
      • 普通函数、箭头函数、生成器函数
      • 构造函数(自定义类)
      • 内置函数(内置构造函数)
  • proto 属性
    • 每一个对象数据类型,都自带一个 proto 属性(隐式原型)
    • 该属性的值指向所属类的原型对象 prototype
    • 对象数据类型
      • 普通对象,数组对象,正则对象,日期对象
      • prototype 原型对象
      • 实例对象
      • 函数本身也是对象
  • Object 类
    • 所有对象都是 Object 内置类的实例
    • Object 也是一个函数, 同样具有 prototype 属性,指向自己的原型对象
    • 它的原型也是一个对象, 因此具有 proto 属性
    • Object 原型对象的 proto 指向 Null (内部设计)

1.2.2 原型链查找机制

  1. 首先查找自己的私有属性,私有中如果存在则使用私有
  2. 私有中如果不存在,则默认基于 proto 找所属类的原型对象
  3. 如果原型对象上没有,则基于原型对象的 proto 继续向上查找,直到找到 Object.prototype 为止

1.2.3 示例代码

function Foo() {
  this.m = 10
  this.n = 24
  this.getM = function () {
    console.log(this.m)
  }
}
Foo.prototype.getM = function () {
  console.log(this.m)
}

Foo.prototype.getN = function () {
  console.log(this.n)
}

let foo1 = new Foo
let foo2 = new Foo
console.log(foo1.getM === foo2.getM)  
console.log(foo1.getN === foo2.getN)
console.log(foo1.__proto__.getN === Foo.prototype.getN)  
console.log(foo1.__proto__.getM === foo2.getM) 
console.log(foo1.getM === Foo.prototype.getM) 
console.log(foo1.constructor) 
console.log(Foo.prototype.__proto__.constructor) 
foo1.getM()  
foo1.__proto__.getM() 
foo2.getN()  
Foo.prototype.getN() 

1.3 重写 new 方法

1.3.1 new 做了什么

  1. 创建实例对象
  2. 执行构造函数, 将 this 指向实例对象
  3. 处理返回值

1.3.2 模拟 new 实现

function Person(name) {
  this.name = name
}
Person.prototype.slogan = function () {
  console.log('前端开发')
}
Person.prototype.sayName = function () {
  console.log(`我们的名称是${this.name}`)
}
//* 原生的写法
// let p1 = new Person('syy')
// p1.slogan()
// p1.sayName()

function _new(Ctor, ...params) {
  //01 创建实例对象
  // let obj = {}
  // obj.__proto__ = Ctor.prototype
  let obj = Object.create(Ctor.prototype)

  // 02 调用构造函数, 改变this 指向
  let ret = Ctor.call(obj, ...params)

  // 03 处理返回结果
  if (ret !== null && /^(object|function)$/.test(typeof ret)) return ret
  return obj
}

let p2 = _new(Person, 'abc')
p2.slogan()
p2.sayName()
console.log(p2 instanceof Person)

1.4 Function 与 Object

Function 与 Object

1.4.1 函数多种角色

  1. 函数
    1. 普通函数调用(堆栈执行及作用域)
    2. 构造函数实例(原型及原型链)
  2. 对象
    1. 键值对
  3. 三种角色之间没有必然的联系,但是最核心的理念是函数就是函数,函数是一等公民

1.4.2 经典语录

  1. Function 是一等公民,在 JS 中存在多种角色,普通函数, 构造函数,对象
  2. 每一个对象都存在 --proto-- 属性, 指向所属类的原型对象(隐式原型,原型链属性)
  3. 每一个函数都存在 prototype 属性, 指向它的原型对象
  4. 所有函数都是 Function 内置类的实例,且 Function 本身也是一个函数
  5. 所有对象都是 Object 的实例,且 Object 本身也是一个函数
  6. Function 与 Object 是二大并行的基类,虽然最终查找落脚点都是 Object 身上
  7. Function.prototype 原型对象是一个匿名函数,但虽然它是一个函数,它的处理机制和原型对象是一样的,它的 --proto-- 属性指向所属类的原型对象,也就是 Object.prototype

1.4.3 无 prototype 属性

  1. Function.prototype 原型是一个匿名函数,但是它没有 prototype 属性
  2. 对象中使用 ES6 语法定义函数 const obj = {say(){}} , say方法不具备
  3. 箭头函数
  4. 不具备 prototype 属性的函数是不能执行 new 操作的

1.5 This 规律

在浏览器平台下运行 JS , 非函数当中的 this 一般都指向 window 
因此这里讨论的是函数执行过程中的 this 
需要注意:ES6+ 的箭头函数中是没有自己 this 的,处理机制就是使用上下文当中的 this 

1.5.1 this 是什么

  1. this 就是当前函数执行的主体(谁执行了函数), 不等于执行上下文或者当前作用域
  2. syy 在拉勾教育讲前端
    1. 讲前端是一个动作(函数)
    2. 拉勾教育(执行上下文)
    3. syy 主体,本次函数在当前执行上下文中执行的 this 指向

1.5.2 常见 this 场景

  1. 事件绑定
  2. 普通函数
  3. 构造函数
  4. 箭头函数
  5. 基于 call bind apply 强制改变 this 指向

1.5.3 规律

  1. 事件绑定
    1. 不论是 DOM2 还是 DOM0 级事件绑定,事件触发时 this 一般都是被操作的元素
  2. 普通函数
    1. 函数执行时查看前端是否有点,如果有点,则点前面的就是执行主体, 没有点就是 window , 严格模式下是 undefined
    2. 特殊情况
      1. 匿名函数中的 this 是 window 或者 undefined
      2. 回调函数中的 this 一般也是 window 或者 undefined
      3. 小括号语法
        1. 如果小括号只有一项,则相当于没加
        2. 如果小括号当中有多项,则取出最后一项,此时相当于拷贝函数,所以调用时主体是 window

1.5.4 练习

(function () {
  console.log(this)
})()

let arr = [1, 3, 5, 7]
obj = {
  name: '拉勾教育'
}
arr.map(function (item, index) {
  console.log(this)
}, obj)
------------------------------------------------------
//? 普通函数调用
let obj = {
  fn: function () {
    console.log(this, 111)
  }
}
let fn = obj.fn;
fn()  // window
obj.fn();  // obj
(10, fn, obj.fn)();
------------------------------------------------------
var a = 3, 
  obj = { a: 5 }
obj.fn = (function () { 
  this.a *= ++a
  return function (b) {
    this.a *= (++a) + b
    console.log(a)
  }
})();
var fn = obj.fn  
obj.fn(6)
fn(4)
console.log(obj.a, a)

二、异步编程与事件环

2.1 名词说明

2.1.1 进程与线程

  1. 进程可以看做是一个应用程序(例如打开浏览器或者浏览器打开一个界面)
  2. 线程是程序当中具体做事情的人, 每个线程同一时刻只能做一件事情
  3. 一个进程当中可以包含多个线程

2.1.2 同步与异步

  1. 同步编程:一件一件的做事情,上一件没有做完,下一件事情不会被处理(单线程)
  2. 异步编程:上一件事情没有处理完,可以直接处理下一件事情(多线程)
  3. JS 基于单线程的 EventLoop 机制也可以实现异步编程

2.1.3 JS 中异步操作

  1. promise(then)
  2. aync await (generator)
  3. requestAnimationFrame
  4. 定时器操作
  5. ajax 网络请求
  6. 事件绑定

2.1.4 JS 单线程

浏览器平台下的 JS 代码是由 JS 引擎执行的, 所以它是单线程的

浏览器是多线程: GUI 渲染线程、JS引擎线程、事件触发线程、定时器触发线程、异步 http请求线程

  1. JS 中大部分代码都是同步编程
  2. JS 可以基于单线程的 EventLoop (事件循环机制) 实现出异步效果

2.2 EventLoop 模型

事件循环模型图

2.2.1 代码执行顺序

  1. 浏览器加载界面之后会开启一个线程来执行 JS,称之叫 JS引擎(主线程)
  2. JS引擎会自上而下的执行 JS 代码,此过程会遇到(定时器,网络请求,事件绑定,promise....)
  3. 遇到上述代码执行之后,浏览器会开启一个 Event Queue(任务|事件)队列 优先级队列结构
  4. 在队列当中存在二个任务队列:微任务 microtask | 宏任务 macrotask
  5. 最终会将遇到的异步任务存放到 Event Queue 队列当中(未执行)
  6. 主线程会继续向下执行同步代码,直到所有同步执行完就会处理异步任务
  7. 进入 Event Queue 当中查找异步任务,找到之后放入主线程中执行(此时主线程又被占用)
  8. 执行完一个异步任务之后,主线程再次空闲,此时再进入 Event Queue 查找余下的异步任务

2.2.2 异步任务执行顺序

  1. 先执行微任务(只要有微任务就不会处理宏任务)
  2. 微任务(一般是谁先放置谁先执行)
  3. 宏任务(一般是谁先到达的谁先执行)

2.2.3 整体顺序

  1. 同步任务
  2. 异步微任务
  3. 异步宏任务
- 如果同步任务执行过程中遇到可执行的异步任务,此时依然需要等到同步任务执行完
- 如果同步任务执行完,还没有可执行的异步任务,此时也只能等待 
- 不论何时放入的微任务,只要异步微任务存在,就永远不会执行异步宏任务

2.2.4 setTimeout

  1. 设置定时器这个操作是同步的
  2. 放置在 Event Queue 中的任务是异步宏任务
  3. 函数调用返回数字,表示当前是第几个定时器
  4. 等待时间设置为 0 时,也不是立即执行,浏览器存在最快的反应时间
  5. 定时器的等待时间到了之后,它的代码可能还不会执行(处于异步队列中,同步任务还未完成执行)

2.2.5 练习

setTimeout(() => {
  console.log('1')
}, 30)

console.log(2)

setTimeout(() => {
  console.log(3)
}, 20)

console.log(4)

console.time('AA')
// 消耗95ms
for (let i = 0; i < 88888888; i++) { }
console.timeEnd('AA')

console.log(5)

setTimeout(() => {
  console.log(6)
}, 18)

console.log(7)

setTimeout(() => {
  console.log(8)
}, 25)

console.log(9)
async function async1() {
  console.log('async1 执行了')
  await async2()
  console.log('async1 结束了')
}

async function async2() {
  console.log('async2')
}

console.log('同步代码执行了')

setTimeout(() => {
  console.log('setTimeout')
}, 0)

async1()

new Promise((resolve) => {
  console.log('promise1')
  resolve()
}).then(() => {
  console.log('promise2')
})

console.log('同步代码结束了')
function fn1() {
  console.log('fn1 start')
  return new Promise((resolve) => {
    resolve('zcegg1')
  })
}
function fn2() {
  console.log('fn2 start')
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('zcegg2')
    }, 10)
  })
}
console.log('a')
setTimeout(async () => {
  console.log('b')
  await fn1()
  console.log('c')
}, 20)
for (let i = 0; i < 88888888; i++) { }  // 90ms  
console.log('d')
fn1().then((result) => {
  console.log('f')
})
fn2().then((result) => {
  console.log('g')
})
setTimeout(() => {
  console.log('h')
}, 0)
console.log('end')
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容