Object——对象
开篇之初我们先抛出几个问题?
- 什么是面向对象?
- function 是一个对象吗?
- 对象分为几类呢?
- 什么是原型对象?
- 构造函数到底是个什么玩意?
- new 到底干了一件什么事?
回想一下这个这些问题你心中是否已有答案呢?在接下来的内容中,我们逐一共同学习!
灵魂质问?到底什么是 js
JavaScript(简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发 Web 页面的脚本语言而出名的,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。
百度是这样说的,这就不是人话,其实本质上 js 是啥?
js 就是专门编写网页交互行为的语言
那 js 是由什么组成的呢,简单来说就一句话
ECMAScript 标准 + webAPI 那么我们今天要一起学习的就是 ECMASciript 中的-Object,他实际上是一个 es 的语言标准
什么是对象-Object
对象其实有两个特点
- 描述现实中一个具体事物的属性和功能的程序结构;
- 内存中同时存储多个数据和方法的一块存储空间。 既然对象是一个具体事物的属性和功能。那么,事物的属性会成为对象的属性, 事物的功能会成为对象的方法;
什么是面向对象
在程序中,都是先用对象封装一个事物的属性和功能。然后,再调用对象 的方法,来执行任务。这就是面向对象,其实在 es6 出来之前,js 总是显得这么合群,其他语言该有的对象的结构,他是一个没沾上,知道 es6 横空出世,我们才有了类这个概念,面向对象也才算是正式打响!
对象的底层到底是什么?
由于是重学前端,我们就不讲怎么创建对象这种百度搜索能有 100 页答案的东西了。
我们来探究一下,对象的底层到底是什么? 咱研究底层之前,我们先来看看数组
数组
数组对象的作用是:使用单独的变量名来存储一系列的值。
数组呢,他还可以分类,分为普通数组,二维数组,hash 数组。普通数组,和二维数组不在赘述了,讲讲这个 hash 数组
hash 数组
哈希数组,又称关联数组,顾名思义,他的数组元素是由[key:value]组成的
var myhash = new Array();
myhash["new"] = "newval";
myhash["new2"] = "newval_2";
由此得出结论:对象底层就是 hash 数组,只不过他在关联数组上有添加了许多包装属性,和方法,这样的结构就导致了,对象有这很多特性比如,对象具有高度的动态性,JavaScript 给使用者在运行时为对象添改状态和行为的能力。(大佬的总结,我照抄)
对象的两类属性特征
在我们日常的认知中,对象只是简单的键值对,其实我们深究的时候发现,对象还提供了一些特征来描述我们的对象成员.
- 描述数据属性的特征
- value:就是属性的值。
- writable:决定属性能否被赋值。
- enumerable:决定 for in 能否枚举该属性。
- configurable:决定该属性能否被删除或者改变特征值。
- 描述访问器属性的特征
- 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 关键字其实干了四件事,也很好记忆
- 创建一个空对象
- 设置新对象的proto继承构造函数的原型对象
- 用新对象调用构造函数,将构造函数中的 this,替换为空对象
- 构造函数会向空对象中添加新的属性和方法。
- 将对象地址返回给 obj
上文中提到 this 指向问题在此梳理一下,几种情况
- 默认绑、隐式绑定(严格/非严格模式)
- 显式绑定
- new 绑定
- 箭头函数绑定
首先声明: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