重学学习前端(二)

304 阅读7分钟

Object——对象

开篇之初我们先抛出几个问题?

  1. 什么是面向对象?
  2. function 是一个对象吗?
  3. 对象分为几类呢?
  4. 什么是原型对象?
  5. 构造函数到底是个什么玩意?
  6. new 到底干了一件什么事?

回想一下这个这些问题你心中是否已有答案呢?在接下来的内容中,我们逐一共同学习!

灵魂质问?到底什么是 js

JavaScript(简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发 Web 页面的脚本语言而出名的,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。

百度是这样说的,这就不是人话,其实本质上 js 是啥?

js 就是专门编写网页交互行为的语言

那 js 是由什么组成的呢,简单来说就一句话

ECMAScript 标准 + webAPI 那么我们今天要一起学习的就是 ECMASciript 中的-Object,他实际上是一个 es 的语言标准

什么是对象-Object

对象其实有两个特点

  1. 描述现实中一个具体事物的属性和功能的程序结构;
  2. 内存中同时存储多个数据和方法的一块存储空间。 既然对象是一个具体事物的属性和功能。那么,事物的属性会成为对象的属性, 事物的功能会成为对象的方法;

什么是面向对象

在程序中,都是先用对象封装一个事物的属性和功能。然后,再调用对象 的方法,来执行任务。这就是面向对象,其实在 es6 出来之前,js 总是显得这么合群,其他语言该有的对象的结构,他是一个没沾上,知道 es6 横空出世,我们才有了类这个概念,面向对象也才算是正式打响!

对象的底层到底是什么?

由于是重学前端,我们就不讲怎么创建对象这种百度搜索能有 100 页答案的东西了。

我们来探究一下,对象的底层到底是什么? 咱研究底层之前,我们先来看看数组

数组

数组对象的作用是:使用单独的变量名来存储一系列的值。

数组呢,他还可以分类,分为普通数组,二维数组,hash 数组。普通数组,和二维数组不在赘述了,讲讲这个 hash 数组

hash 数组

哈希数组,又称关联数组,顾名思义,他的数组元素是由[key:value]组成的

var myhash = new Array();
myhash["new"] = "newval";
myhash["new2"] = "newval_2";

由此得出结论:对象底层就是 hash 数组,只不过他在关联数组上有添加了许多包装属性,和方法,这样的结构就导致了,对象有这很多特性比如,对象具有高度的动态性,JavaScript 给使用者在运行时为对象添改状态和行为的能力。(大佬的总结,我照抄)

对象的两类属性特征

在我们日常的认知中,对象只是简单的键值对,其实我们深究的时候发现,对象还提供了一些特征来描述我们的对象成员.

  1. 描述数据属性的特征
    • value:就是属性的值。
    • writable:决定属性能否被赋值。
    • enumerable:决定 for in 能否枚举该属性。
    • configurable:决定该属性能否被删除或者改变特征值。
  2. 描述访问器属性的特征
    • getter:函数或 undefined,在取属性值时被调用。
    • setter:函数或 undefined,在设置属性值时被调用。
    • enumerable:决定 for in 能否枚举该属性。
    • configurable:决定该属性能否被删除或者改变特征值。

我们可以通过内置的 Object.getOwnPropertyDescripte 来查看

var actions = {
  b: 1,
  c: 2,
};
var d = Object.getOwnPropertyDescriptor(actions, "b");
console.log(d);

如果想要修改我们可以通过内置的 Object.defineProperty 来修改,这也是 vue 能实现数据劫持的原理

var value = { a: 1 };
Object.defineProperty(value, "b", {
  value: 2,
  writable: false,
  enumerable: false,
  configurable: true,
});

var obja = Object.getOwnPropertyDescriptor(value, "a");
var objb = Object.getOwnPropertyDescriptor(value, "b");
//我们发现赋值为3已经不能改了
value.b = 3;
console.log(value.b, obja, objb); // 2

我们发现,其实 vue 的响应式核心就是 Object.defineProperty 这个方法能监听到值的改变,然后去通知已经订阅的各个方法,进行相应的操作,来达到改变视图的目的

var value = { b: 1 };
var tm = null;
Object.defineProperty(value, "b", {
  get: function () {
    console.log("我取值的时候被调用了一次");
    return tm;
  },
  set: function (a) {
    console.log("我赋值的时候被调用一次");
    tm = a;
  },
});
//赋值
value.b = 3;
//取值
console.log(value.b);

面向对象之继承

用大白话解释:继承就是父对象的成员,子对象无需创建,就可直接使用

原型就是新对象持有一个放公用属性和方法的的引用的地方,注意并不真的去复制一个原型对象,而是使得新对象持有一个原型的引用,每个构造函数在出生的时候(constructor)都附送一个原型对象(prototype)

上述概念,就回答了什么叫做原型对象。这里又有一个老生常谈的名字,构造函数;

构造函数和原型以及对象之间的关系如下图所示:

构造函数

我们发现其实他们之间其实就是靠着新对象用双下划线 proto 继承原型对象,构造函数用 prototyoe 引用原型对象

function Person(name) {
  this.name = name;
}
// 通过构造函数的 Person 的 prototype 属性找到 Person 的原型对象
Person.prototype.eat = function () {
  console.log("吃饭");
};

let Person1 = new Person("aaa", 24);
let Person2 = new Person("bbb", 24);

console.log(Person1.eat === Person2.eat); //发现相等
console.log(Person1);
console.log(Person2);

结构图

在上图中我们发现,他的双下划线 proto 中还有双下划线 proto 这就形成了链条状我们把它叫做原型链;

理解上上述原理之后,我们就可以轻松发现,如果要实现当下流行的这些 构造函数继承、组合继承、原型继承、寄生式继承、寄生组合式继承 其实不过是改变他的.prototype 指向,从而改变双下划线 proto 的指向而已(我是这样理解的,错误之处大佬指出),当然在 es6 大行其道的今天,我们一个 class 和 extends 全部干掉了这些花里胡哨!

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(this.name + " makes a noise.");
  }
}
class Dog extends Animal {
  constructor(name) {
    super(name); // call the super class constructor and pass in the name parameter
  }
  speak() {
    console.log(this.name + " barks.");
  }
}
let d = new Dog("Mitzie");
d.speak(); // Mitzie barks.

new 到底干了一件什么事情?

我的理解这个 new 关键字其实干了四件事,也很好记忆

  1. 创建一个空对象
  2. 设置新对象的proto继承构造函数的原型对象
  3. 用新对象调用构造函数,将构造函数中的 this,替换为空对象
  4. 构造函数会向空对象中添加新的属性和方法。
  5. 将对象地址返回给 obj

上文中提到 this 指向问题在此梳理一下,几种情况

  1. 默认绑、隐式绑定(严格/非严格模式)
  2. 显式绑定
  3. new 绑定
  4. 箭头函数绑定

首先声明:this 的确定是在运行时确定也就是调用时,调用位置就是函数在代码中被调用的位置(而不是声明的位置)

其实我的理解是 this 是在将这个函数压入执行环境栈的时候就已经被确定了,执行的时候只不过从已经确定的 this,指向的地方去拿对象替代 this(之前这个 this 的确定时间问题,被一个阿里大佬问到过,当时也是云里雾里,特地去网上查了很多资料也是各种版本,在这里说一下我的理解,不对之处请大佬指出)

默认绑定

默认指向调用这个方法的对象,如果没有对象去调用,那就不用想了,就指向全局,如果是严格模式,那就是 undefined

function foo() {
  console.log(this.a);
}

var obj = {
  a: 2,
  foo: foo,
};
//我们发现前面有对象调用
obj.foo(); // 2
var obj = {
  b: function () {
    console.log(this);
  },
};
function b(b) {
  //发现前面没有调用对象调用,那就指向window
  b();
}
2;
b(obj.b); //window

显式绑定

通过 call,apply,bind 去绑定 this,就叫做显示绑定,这些为啥能实现显示绑定;

function foo() {
  console.log(this.a);
}
var obj = {
  a: 2,
};

foo.call(obj); // 2用call强制绑定了一下

new 绑定

function foo(a) {
  this.a = a;
}
var bar = new foo(2); //在new的时候this被绑定到了这个新对象
console.log(bar.a); // 2

箭头函数绑定

var foo = () => {
  console.log(this);
};
var a = {
  b: foo,
};
//这时发现this无法被改变了
a.b(); //window