由此展开得到了这样一个思维导图:
对象主要分为这么三大类:
-
内置对象:ECMAScript规范中定义的类或对象,比如
Object
,Array
,Date
等。 -
宿主对象:由javascript解释器所嵌入的宿主环境提供。比如浏览器环境会提供
window
,HTMLElement
等浏览器特有的宿主对象。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对象的构造函数来获得对象实例。比如:
- 创建内置对象实例
var o = new Object();
- 创建自定义对象实例
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.propA
是undefined
,因为子类实例不是父类的实例,无法继承父类原型属性。
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前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
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到客户端有那些方式?
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
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)]
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算