理解面向对象和事件循环

592 阅读6分钟

一、名词解释

思考:什么是对象、类、实例?

  1. 一切皆对象,在众多的对象当中我们会发现一些对象,它们具有相同的特征
  2. 此时就可以做抽象,最终产出类
  3. 实例就是我们new之后的一个产物(对象--->类--->实例) 思考:什么是普通函数,什么是构造函数?首先通过一段代码说明:
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) // 30

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

图解代码执行过程: image.png 分析:new操作到底做了什么?

当执行 new 操作的时候浏览器会自动开辟一个内存空间,用于存放实例对象。然后与 this 进行关联。简述:创建对象--关联this--返回对象
返回对象的规则:

  • 构造函数中无return时,默认返回this
  • 构造函数中有return时,若为基本数据类型忽略它,返回this,若为引用类型,返回当前引用对象的值

二、面向对象(非Class)

1. prototype 属性

  • 每个函数(除箭头)数据类型,都自带一个prototype属性,指向原型对象(Function除外)
  • 每一个原型对象都自带一个constructor 属性,指向当前构造函数本身
  • 函数类数据类型:普通函数、箭头函数、生成器函数、构造函数(自定义类)、内置函数(内置构造函数)

2. __proto__属性

  • 每个对象数据类型,都自带一个__proto__属性,指向当前实例所属类的原型对象(prototype
  • 对象数据类型:普通对象、数组对象、正则、prototype原型对象、实例对象、函数也是对象

3. Object类

  • 所有对象如果不知道是new谁来的,那么它就是Object的一个实例
  • Object 本身也是一个函数,因此同样具有prototype属性,指向它自己的原型对象
  • Object的原型对象本身也是一个对象,因此它身上也具有一个__proto__属性,内部设计它指向null 以一个实例分析以上的概念:
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

图解以上代码中原型和原型链的查找: image.png

Foo.prototype.__proto__  === Object.prototype // true
Object.prototype.__proto__ === null // true
Foo.prototype.constructor === Foo // true
Object.prototype.constructor === Object // true

实例属性的查找规则:

  • 首先会找自己的私有属性,如果有则直接使用
  • 若私有属性中不存在,则默认会基于__proto__属性找到实例所属类的原型对象
  • 若原型对象上也没有,则继续基于原型对象的__proto__往上接着找,直到找到Object.prototype为止

三、Function 与 Object

1、函数有哪些?

  1. 普通函数调用(堆栈作用域及作用域链)
  2. 构造函数(原型及原型链)
  3. 对象(键值对)[所谓的高阶应用] 把函数当作参数或返回值进行使用 上述函数的三种角色之间并没有必然的联系,但是函数是一等公民(默认函数就是函数,除非考虑函数的高阶应用可以把它看作对象更好理解和使用)

2、重点语录

  1. Function 是一等公民,虽然有很多种身份,但最重要的还是函数
  2. 每个对象都存在__proto__属性,指向所属类的原型对象
  3. 每个函数存在prototype属性(有例外),指向它的原型对象【有的函数没有原型对象】
  4. 所有对象如果不知道谁是爹,那么就都安在Object身上 ,且Object本身也是一个函数
  5. FunctionObject是JS当中二大并行的基类,虽然最终查找的落脚点都是Object
  6. 所有的函数都是new Function来的
  7. Function.prototype是一个匿名函数,但是它的处理机制和其它的原型对象是一样的(指向Object.prototype), 即:Function.prototype.__proto__ === Object.prototype

3、实例演示

function Foo() {}
let foo = new Foo

图解FunctionObject的关联: image.png

4、函数的prototype属性

若失去prototype属性,则不能执行new操作。 哪些函数不具备prototype属性?(即内部内部没有this)

  • 箭头函数
  • Function.prototype
  • 对象中的函数简写
// 对象中的函数简写
const obj = {
    foo() {
        // 这个函数也没有prototype
    }
}
new obj.foo()

四、this

1. this是什么?

this是当前函数的执行主体(谁执行了这个函数),不等同于执行上下文,也不等同于作用域 通俗理解:小明在教室讲课。

  • 其中讲课是一个动作(函数)
  • 教室提供了黑板,讲台等等(执行上下文)
  • 小明就是主体,本次函数在当前执行上下文里的this指向

2. 常出现this的场景

  • 事件绑定(oBtn.onclick = function() { this // 指当前DOM对象oBtn})
  • 普通函数调用
  • 构造函数(this一般指向new Foo的实例对象)
  • 箭头函数(不具备this
  • 基于call/bind/apply 强制改变this的指向(obj.foo.call(obj2) // this指向obj2

3. this指向规律

  1. 函数执行的时候需要查看函数的前端是否有. 如果有,则点前面的对象就执行主体this,如果没有. 一般就是windowundefined(严格模式)
  2. 特殊情况
  • 匿名函数中的thiswindow或者undefined
(function () {
    console.log(this) // window
})()
  • 回调函数中的thiswindow或者undefined
let arr = [1]
obj = { name: '小明' }
arr.map(function(item, index) {
    console.log(this) // { name: '小明' }
}, obj) // obj修改了this指向
  • 小括号括号语法
let obj = {
    fn: function() {
        console.log(this, 111)
    }
}
let fn = obj.fn
fn() // 没. this指向window
obj.fn() // 有. this指向obj
(obj.fn)() // 如果小括号里只有一项,那么和没写小括号是一样的
(10, 20 ,30) // 30
// 如果小括号里有多项,那么它会返回最后一项
// 且有一个现象,它相当于是对最后一项做了一次拷贝
// 即:把一个数据从一个地址挪到另一个地方保存,和obj没关系了
// 下面的代码就好像是在执行 fn() ---> window
(10, fn, obj.fn)() // window{...}  111

4. this实战

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)

图解代码的执行过程: image.png

五、JS 异步编程

六、EventLoop 模型

01-EventLoop 模型.png

七、执行顺序题目练习

1、经典题目

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)

2、主线程占用

// 死循环是将主线程彻底占用,其它所有事情不在处理
// 跑出异常只会影响下面的同步任务,已经放置在队列当中的任务会继续执行
setTimeout(() => {
  console.log(1)
}, 0)

console.log(2)
while (true) { }
// throw new Error('手动抛出异常')
// console.log(a)

console.log(3)

setTimeout(() => {
  console.log(4)
}, 10)

console.log(5)

八、Promise

九、async 与 await

十、练习

var a = [];
for(var i = 0; i < 10; i++) {
  a[i] = function() {
    console.log(i)
  }
}
a[6]()

image.png

var a = 10;
var obj = {
  a: 20,
  fn() {
    setTimeout(() => {
      console.log(this.a)
    })
  }
}
obj.fn()

image.png