「思维导图学前端 」6k字一文搞懂Javascript对象,原型

由此展开得到了这样一个思维导图:

js对象

对象的分类


对象主要分为这么三大类:

  • 内置对象:ECMAScript规范中定义的类或对象,比如Object, Array, Date等。

  • 宿主对象:由javascript解释器所嵌入的宿主环境提供。比如浏览器环境会提供windowHTMLElement等浏览器特有的宿主对象。Nodejs会提供global全局对象

  • 自定义对象:由javascript开发者自行创建的对象,用以实现特定业务。就比如我们熟悉的Vue,它就是一个自定义对象。我们可以对Vue这个对象进行实例化,用于生成基于Vue的应用。

对象的三个重要概念


javascript在ES6之前没有class关键字,但这不影响javascript可以实现面向对象编程,javascript的类名对应构造函数名。

在ES6之前,如果我们要定义一个类,其实是借助函数来实现的。

function Person(name) {

this.name = name;

}

Person.prototype.sayHello = function() {

console.log(this.name + ‘: hello!’);

}

var person = new Person(‘Faker’);

person.sayHello();

ES6明确定义了class关键字。

class Person {

constructor(name) {

this.name = name;

}

sayHello() {

console.log(this.name + ‘: hello!’);

}

}

var person = new Person(‘Faker’);

person.sayHello();

原型

原型是类的核心,用于定义类的属性和方法,这些属性和方法会被实例继承。

定义原型属性和方法需要用到构造函数的prototype属性,通过prototype属性可以获取到原型对象的引用,然后就可以扩展原型对象了。

function Person(name) {

this.name = name;

}

Person.prototype.sexList = [‘man’, ‘woman’];

Person.prototype.sayHello = function() {

console.log(this.name + ‘: hello!’);

}

实例

类是抽象的概念,相当于一个模板,而实例是类的具体表现。就比如Person是一个类,而根据Person类,我们可以实例化多个对象,可能有小明,小红,小王等等,类的实例都是一个个独立的个体,但是他们都有共同的原型。

var xiaoMing = new Person(‘小明’);

var xiaoHong = new Person(‘小红’);

// 拥有同一个原型

Object.getPrototypeOf(xiaoMing) === Object.getPrototypeOf(xiaoHong); // true

如何创建对象


对象直接量

对象直接量也称为对象字面量。直接量就是不需要实例化,直接写键值对即可创建对象,堪称“简单粗暴”。

var xiaoMing = { name: ‘小明’ };

每写一个对象直接量相当于创建了一个新的对象。即使两个对象直接量看起来一模一样,它们指向的堆内存地址也是不一样的,而对象是按引用访问的,所以这两个对象是不相等的。

var xiaoMing1 = { name: ‘小明’ };

var xiaoMing2 = { name: ‘小明’ };

xiaoMing1 === xiaoMing2; // false

new 构造函数

可以通过关键词new调用javascript对象的构造函数来获得对象实例。比如:

  1. 创建内置对象实例

var o = new Object();

  1. 创建自定义对象实例

function Person(name) {

this.name = name;

};

new Person(‘Faker’);

Object.create

Object.create用于创建一个对象,接受两个参数,使用语法如下;

Object.create(proto[, propertiesObject]);

第一个参数proto用于指定新创建对象的原型;

第二个参数propertiesObject是新创建对象的属性名及属性描述符组成的对象。

proto可以指定为null,但是意味着新对象的原型是null,它不会继承Object的方法,比如toString()等。

propertiesObject参数与Object.defineProperties方法的第二个参数格式相同。

var o = Object.create(Object.prototype, {

// foo会成为所创建对象的数据属性

foo: {

writable:true,

configurable:true,

value: “hello”

},

// bar会成为所创建对象的访问器属性

bar: {

configurable: false,

get: function() { return 10 },

set: function(value) {

console.log(“Setting o.bar to”, value);

}

}

});

属性查询和设置


属性查询

属性查询也可以称为属性访问。在javascript中,对象属性查询非常灵活,支持点号查询,也支持字符串索引查询(之所以说是“字符串索引”,是因为写法看起像数组,索引是字符串而不是数字)。

通过点号加属性名访问属性的行为很像一些静态类型语言,如java,C等。属性名是javascript标识符,必须直接写在属性访问表达式中,不能动态访问。

var o = { name: ‘小明’ };

o.name; // “小明”

而根据字符串索引查询对象属性就比较灵活了,属性名就是字符串表达式的值,而一个表达式是可以接受变量的,这意味着可以动态访问属性,这赋予了javascript程序员很大的灵活性。下面是一个很简单的示例,而这种特性在业务实践中作用很大,比如深拷贝的实现,你往往不知道你要拷贝的对象中有哪些属性。

var o = { chineseName: ‘小明’, englishName: ‘XiaoMing’ };

[‘chinese’, ‘english’].forEach(lang => {

var property = lang + ‘Name’;

console.log(o[property]); // 这里使用了字符串索引访问对象属性

})

对了,属性查询不仅可以查询自由属性,也可以查询继承属性。

var protoObj = { age: 18 };

var o = Object.create(protoObj);

o.age; // 18,这里访问的是原型属性,也就是继承得到的属性

属性设置

通过属性访问表达式,我们可以得到属性的引用,就可以据此设置属性了。这里主要注意一下只读属性和继承属性即可,细节不再展开。

原型和继承


原型

前面也提到了,原型是实现继承的基础。那么如何去理解原型呢?

首先,要明确原型概念中的三角关系,三个主角分别是构造函数,原型,实例。我这里画了一张比较简单的图来帮助理解下。

原型三角关系

原型这东西吧,我感觉“没人能帮你理解,只有你自己去试过才是懂了”。

不过这里说说我刚学习原型时的疑惑,疑惑的是为什么构造函数有属性prototype指向原型,而实例又可以通过__proto__指向原型,究竟prototype__proto__谁是原型?其实这明显是没有理解对象是按引用访问这个特点了。原型对象永远只有一个,它存储于堆内存中,而构造函数的prototype属性只是获得了原型的引用,通过这个引用可以操作原型。

同样地,__proto__也只是原型的引用,但是要注意了,__proto__不是ECMAScript规范里的东西,所以千万不要用在生产环境中。

至于为什么不可以通过__proto__访问原型,原因也很简单。通过实例直接获得了原型的访问和修改权限,这本身是一件很危险的事情。

举个例子,这里有一个类LatinDancer,意思是拉丁舞者。经过实例化操作,得到了多个拉丁舞者。

function LatinDancer(name) {

this.name = name;

};

LatinDancer.prototype.dance = function() {

console.log(this.name + ‘跳拉丁舞…’);

}

var dancer1 = new LatinDancer(‘小明’);

var dancer2 = new LatinDancer(‘小红’);

var dancer3 = new LatinDancer(‘小王’);

dancer1.dance(); // 小明跳拉丁舞…

dancer2.dance(); // 小红跳拉丁舞…

dancer3.dance(); // 小王跳拉丁舞…

大家欢快地跳着拉丁舞,突然小王这个家伙心血来潮,说:“我要做b-boy,我要跳Breaking”。于是,他私下改了原型方法dance()

dancer3.proto.dance = function() {

console.log(this.name + ‘跳breaking…’);

}

dancer1.dance(); // 小明跳breaking…

dancer2.dance(); // 小红跳breaking…

dancer3.dance(); // 小王跳breaking…

这个时候就不对劲了,小明和小红正跳着拉丁,突然身体不受控制了,跳起了Breaking,心里暗骂:“沃尼玛,劳资不是跳拉丁的吗?”

看我表情

这里只是举个例子哈,没有对任何舞种或者舞者不敬的意思,抱歉抱歉。

所以,大家应该也明白了为什么不能使用__proto__了吧。

原型链

在javascript中,任何对象都有原型,除了Object.prototype,它没有原型,或者说它的原型是null

那么什么是原型链呢?javascript程序在查找一个对象的属性或方法时,会首先在对象本身上进行查找,如果找不到则会去对象的原型上进行查找。按照这样一个递归关系,如果原型上找不到,就会到原型的原型上找,这样一直查找下去,就会形成一个链,它的终点是null

还要注意的一点是,构造函数也是一个对象,也存在原型,它的原型可以通过Function.prototype获得,而Function.prototype的原型则可以通过Object.prototype获得。

继承

说到继承,可能大家脑子里已经冒出来“原型链继承”,“借用构造函数继承”,“寄生式继承”,“原型式继承”,“寄生组合继承”这些概念了吧。说实话,一开始我也是这么记忆,但是发现好像不是那么容易理解啊。最后,我发现,只要从原型三角关系入手,就能理清实现继承的思路。

原型三角关系

我们知道,对象实例能访问的属性和方法一共有三个来源,分别是:调用构造函数时挂载到实例上的属性,原型属性,对象实例化后自身新增的属性。

很明显,第三个来源不是用来做继承的,那么前两个来源用来做继承分别有什么优缺点呢?很明显,如果只基于其中一种来源做继承,都不可能全面地继承来自父类的属性或方法。

首先明确下继承中三个主体:父类子类子类实例。那么怎么才能让子类实例和父类搭上关系呢?

原型链继承

所谓继承,简单说就是能通过子类实例访问父类的属性和方法。而利用原型链可以达成这样的目的,所以只要父类原型、子类原型、子类实例形成原型链关系即可。

原型链继承

代码示例:

function Father() {

this.nationality = ‘Han’;

};

Father.prototype.propA = ‘我是父类原型上的属性’;

function Child() {};

Child.prototype = new Father();

Child.prototype.constructor = Child; // 修正原型上的constructor属性

Child.prototype.propB = ‘我是子类原型上的属性’;

var child = new Child();

console.log(child.propA, child.propB, child.nationality); // 都可以访问到

child instanceof Father; // true

可以看到,在上述代码中,我们做了这样一个特殊处理Child.prototype.constructor = Child;。一方面是为了保证constructor的指向正确,毕竟实例由子类实例化得来,如果constructor指向父类构造函数也不太合适吧。另一方面是为了防止某些方法显示调用constructor时带来的麻烦。具体解释见Why is it necessary to set the prototype constructor?

关键点:让子类原型成为父类的实例,子类实例也是父类的实例。

缺点:实例化时无法向父类构造函数传参。

借用构造函数

在调用子类构造函数时,通过call调用父类构造函数,同时指定this值。

function Father() {

this.nationality = ‘Han’;

};

Father.prototype.propA = ‘我是父类原型上的属性’;

function Child() {

Father.call(this);

};

Child.prototype.propB = ‘我是子类原型上的属性’;

var child = new Child();

console.log(child.propA, child.propB, child.nationality);

这里的child.propAundefined,因为子类实例不是父类的实例,无法继承父类原型属性。

child instanceof Father; // false

关键点:构造函数的复用。

缺点:子类实例不是父类的实例,无法继承父类原型属性。

组合继承

所谓组合继承,就是综合上述两种方法。实现代码如下:

function Father() {

this.nationality = ‘Han’;

};

Father.prototype.propA = ‘我是父类原型上的属性’;

function Child() {

Father.call(this);

};

Child.prototype = new Father();

Child.prototype.constructor = Child; // 修正原型上的constructor属性

Child.prototype.propB = ‘我是子类原型上的属性’;

var child = new Child();

console.log(child.propA, child.propB, child.nationality); // 都能访问到

一眼看上去没什么问题,但是Father()构造函数其实是被调用了两次的。第一次发生在Child.prototype = new Father();,此时子类原型成为了父类实例,执行父类构造函数Father()时,获得了实例属性nationality;第二次发生在var child = new Child();,此时执行子类构造函数Child(),而Child()中通过call()调用了父类构造函数,所以子类实例也获得了实例属性nationality。这样理解起来可能有点晦涩难懂,我们可以看看子类实例的对象结构:

组合继承的弊端

可以看到,子类实例和子类原型上都挂载了执行父类构造函数时获得的属性nationality。然而我们做继承的目的是很单纯的,即“让子类继承父类属性和方法”,但并不应该给子类原型挂载不必要的属性而导致污染子类原型。

有人会说“这么一点副作用怕什么”。当然,对于这么简单的父类而言,这种副作用微乎其微。假设父类有几百个属性或方法呢,这种白白耗费性能和内存的行为是有必要的吗?答案显而易见。

关键点:实例属性和原型属性都得以继承。

缺点:父类构造函数被执行了两次,污染了子类原型。

原型式继承

原型式继承是相对于原型链继承而言的,与原型链继承的不同点在于,子类原型在创建时,不会执行父类构造函数,是一个纯粹的空对象。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

js基础

1)对js的理解?
2)请说出以下代码输出的值?
3)把以下代码,改写成依次输出0-9
4)如何区分数组对象,普通对象,函数对象
5)面向对象、面向过程
6)面向对象的三大基本特性
7)XML和JSON的区别?
8)Web Worker 和webSocket?
9)Javascript垃圾回收方法?
10)new操作符具体干了什么呢?
11)js延迟加载的方式有哪些?
12)WEB应用从服务器主动推送Data到客户端有那些方式?

js基础.PNG

前16.PNG

CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

内容对你有帮助,可以添加V获取:vip1024c (备注前端)**
[外链图片转存中…(img-AJc6VDk4-1712207332977)]

js基础

1)对js的理解?
2)请说出以下代码输出的值?
3)把以下代码,改写成依次输出0-9
4)如何区分数组对象,普通对象,函数对象
5)面向对象、面向过程
6)面向对象的三大基本特性
7)XML和JSON的区别?
8)Web Worker 和webSocket?
9)Javascript垃圾回收方法?
10)new操作符具体干了什么呢?
11)js延迟加载的方式有哪些?
12)WEB应用从服务器主动推送Data到客户端有那些方式?

[外链图片转存中…(img-Np02BYe8-1712207332977)]

[外链图片转存中…(img-KE6vJopD-1712207332977)]

CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值