JavaScript 类型、原型与继承学习笔记
这篇笔记中有什么:
️JavaScript的极简介绍
️JavaScript中数据类型的简单梳理
️JavaScript中的面向对象原理
这篇笔记中没有什么:
JavaScript的具体语法
JavaScript通过各种内置对象实现的其他特性
一、概览
- 解释型,或者说即时编译型( Just-In-Time Compiled )语言。
- 多范式动态语言,原生支持函数式编程,通过原型链支持面向对象编程。
- 其实是和Java是完全不同的东西。设计中有参考Java的数据结构和内存管理、C语言的基本语法,但理念上并不相似。
- 最开始是专门为浏览器设计的一门脚本语言,但现在也被用于很多其他环境,甚至可以在任意搭载了JavaScript引擎的设备中执行。
二、数据类型
1. JavaScript中的数据类型
最新的标准中,定义了8种数据类型。其中包括:
- 7种基本类型:Number、String、Boolean、BigInt、Null、Undefined以及ES2016新增的Symbol。
- 1种复杂类型:Object。
2. 什么是基本类型(Primitive Data Type)
2.1 概念
基本数据类型,有些版本也译为原始数据类型。
什么是基本类型?看一下MDN上给出的定义:
In JavaScript, a primitive (primitive value, primitive data type) is data that is not an object and has no methods.
基本类型是最底层的类型,不是对象,没有方法。
所有基本数据类型的值都是不可改变的——可以为变量赋一个新值、覆盖原来的值,但是无法直接修改值本身。
这一点对于number、boolean来说都很直观,但是对于字符串来说可能需要格外注意:同一块内存中的一个字符串是不可以部分修改的,一定是整体重新赋值。
var a = "hello"; // 一个string类型的变量,值为“hello”
console.log(a); // hello
console.log(typeof a); // string
a[0] = "H";
console.log(a); // hello
var c = a; // world
c = c + " world"; // 这里,并没有改变本来的hello,而是开辟了新的内存空间,构造了新的基本值“hello world”
console.log(c); // hello world
2.2 七个基本类型
布尔 boolean
- 取值为
true和false。 0、""、NaN、null、undefined也会被转换为false。
- 取值为
Null
- Null类型只有一个值:
null。表示未被声明的值。 - 注意:由于历史原因,typeof null的结果是
"object"。
- Null类型只有一个值:
undefined
- 未初始化的值(声明了但是没有赋值)。
var a;
console.log(typeof a); // undefined
console.log(typeof a); // "undefined"
数字 number
- 64位双精度浮点数(并没有整数和浮点数的区别)。
大整数 bigint
- 可以用任意精度表示整数。
- 通过在整数末尾附加n或调用构造函数来创建。
- 不可以与Number混合运算,会报类型错误。需要先进行转换。
字符串 string
- Unicode字符序列。
符号 Symbol
- 可以用来作为Object的key的值(默认私有)。
- 通过
Symbol()函数构造,每个从该函数返回的symbol值都是唯一的。 - 可以使用可选的字符串来描述symbol,仅仅相当于注释,可用于调试。
var sym1 = Symbol("abc");
var sym2 = Symbol("abc");
console.log(sym1 == sym2); // false
console.log(sym1 === sym2); // false
2.3 基本类型封装对象
接触了一些JavaScript的代码,又了解了它对类型的分类之后,可能会感到非常困惑:基本数据类型不是对象,没有方法,那么为什么又经常会看到对字符串、数字等“基本类型”的变量调用方法呢?
如下面的例子:
var str = "hello";
console.log(typeof str); // string
console.log(str.charAt(2)); // "l"
可以看到,str的类型确实是基本类型string,理论上来说并不是对象。但是我们实际上却能够通过点运算符调用一些为字符串定义的方法。这是为什么呢?
其实,执行str.charAt(2)的时候发生了很多事情,远比我们所看到的一个“普通的调用”要复杂。
Java中有基本类型包装类的概念。比如:Integer是对基本int类型进行了封装的包装类,提供一些额外的函数。
在JavaScript中,原理也是如此,只是在形式上进行了隐藏。JavaScript中,定义了原生对象String,作为基本类型string的封装对象。我们看到的charAt()方法,其实是String对象中的定义。当我们试图访问基本类型的属性和方法时,JavaScript会自动为基本类型值封装出一个封装对象,之后从封装对象中去访问属性、方法。而且,这个对象是临时的,调用完属性之后,包装对象就会被丢弃。
这也就解释了一件事:为什么给基本类型添加属性不会报错,但是并不会有任何效果。因为,添加的属性其实添加在了临时对象上,而临时对象很快就被销毁了,并不会对原始值造成影响。
封装对象有: String、Number、Boolean 和 Symbol。
我们也可以通过new去显性地创建包装对象(除了Symbol)。
var str = "hello";
var num = 23;
var bool = false;
var S = new String(str)
var N = new Number(num)
var B = new Boolean(bool);
console.log(typeof S); //object
console.log(typeof N); // object
console.log(typeof B); // object
一般来说,将这件事托付给JavaScript引擎去做更好一些,手动创建封装对象可能会导致很多问题。
包装对象作为一种技术上的实现细节,不需要过多关注。但是了解这个原理有助于我们更好地理解和使用基本数据类型。
3. 什么是对象类型(Object)
3.1 四类特殊对象
函数 Function
- 每个JavaScript函数实际上都是一个
Function对象 - JavaScript中,函数是“一等公民”,也就是说,函数可以被赋值给变量,可以被作为参数,可以被作为返回值。(这个特性Lua中也有)
- 因此,可以将函数理解为,一种附加了可被调用功能的普通对象。
- 每个JavaScript函数实际上都是一个
数组 Array
- 用于构造数组的全局对象。数组是一种类列表的对象。
Array的长度可变,元素类型任意,因此可能是非密集型的。数组索引只能是整数,索引从0开始 - 访问元素时通过中括号
- 日期 Date
- 通过
new操作符创建
- 用于构造数组的全局对象。数组是一种类列表的对象。
正则 RegExp
- 用于将文本与一个模式进行匹配
3.2 对象是属性的集合
对象是一种特殊的数据,可以看做是一组属性的集合。属性可以是数据,也可以是函数(此时称为方法)。每个属性有一个名称和一个值,可以近似看成是一个键值对。名称通常是字符串,也可以是Symbol。
3.3 对象的创建
var obj = new Object(); // 通过new操作符
var obj = {}; // 通过对象字面量(object literal)
3.4 对象的访问
有两种方式来访问对象的属性,一种是通过点操作符,一种是通过中括号。
var a = {};
a["age"] = 3; // 添加新的属性
console.log(a.age); // 3
for(i in a){
console.log(i); // "age"
console.log(a[i]); // 3
}
对于对象的方法,如果加括号,是返回调用结果;如果不加括号,是返回方法本身,可以赋值给其他变量。
var a = {name : "a"};
a.sayHello = function(){
console.log(this.name + ":hello");
}
var b = {name : "b"};
b.saySomething = a.sayHello;
b.saySomething(); //"b:hello"
注:函数作为对象的方法被调用时,this值就是该对象。
3.5 引用类型
有些地方会用到引用类型这个概念来指代Object类型。要理解这个说法,就需要理解javascript中变量的访问方式。
基本数据类型的值是按值访问的
引用类型的值是按引用访问的
按值访问意味着值不可变、比较是值与值之间的比较、变量的标识符和值都存放在栈内存中。赋值时,进行的是值的拷贝,赋值操作后,两个变量互相不影响。
按引用访问以为着值可变(Object的属性可以动态的增删改)、比较是引用的比较(两个不同的空对象是不相等的)、引用类型的值保存在堆内存中,栈内存里保存的是地址。赋值时,进行的是地址值的拷贝,复制操作后两个变量指向同一个对象。通过其中一个变量修改对象属性的话,通过另一个变量去访问属性,也是已经被改变过的。
3.6 和Lua中Table的比较
Object类型的概念和Lua中的table类型比较相似。变量保存的都是引用,数据组织都是类键值对的形式。table中用原表(metatable)来实现面向对象的概念,Javascript中则是用原型(prototype)。
目前看到的相似点比较多,差异性有待进一步比较。
三、面向对象
1. 意义
编程时经常会有重用的需求。我们希望能够大规模构建同种结构的对象,有时我们还希望能够基于某个已有的对象构建新的对象,只重写或添加部分新的属性。这就需要“类型和继承”的概念。
Javascript中并没有class实现,除了基本类型之外只有Object这一种类型。但是我们可以通过原型继承的方式实现面向对象的需求。
注:ECMAScript6中引入了一套新的关键字用来实现class。但是底层原理仍然是基于原型的。此处先不提。
2. 原型与继承
Javascript中,每个对象都有一个特殊的隐藏属性[[Prototype]],它要么为null,要么就是对另一个对象的引用。被引用的对象,称为这个对象的原型对象。
原型对象也有一个自己的[[Prototype]],层层向上,直到一个对象的原型对象为null。
可以很容易地推断出,这是一个链状,或者说树状的关系。null是没有原型的,是所有原型链的终点。
如前文所说,JavaScript中的Object是属性的集合。原型属性将多个Obeject串连成链。当试图访问一个对象的属性时,会首先在该对象中搜索,如果没有找到,那么会沿着原型链一路搜索上去,直到在某个原型上找到了该属性或者到达了原型链的末尾。Javascript就是通过这种形式,实现了继承。
从原理来看,可以很自然地明白,原型链前端的属性会屏蔽掉后端的同名属性。
函数在JavaScript中是一等公民,函数的继承与和其他属性的继承没有区别。
需要注意的是,在调用一个方法obj.method()时,即使方法是从obj的原型中获取的,this始终引用obj。方法始终与当前对象一起使用。
3. 自定义对象
如何创建类似对象
继承一个对象可以通过原型,那么如何可复用地产生对象呢?
可以使用函数来模拟我们想要的“类”。实现一个类似于构造器的函数,在这个函数中定义并返回我们想要的对象。这样,每次调用这个函数的时候我们都可以产生一个同“类”的新对象。
function makePerson(name, age){
return {
name: name,
age: age,
getIntro:function(){
return "Name:" + this.name + " Age:" + this.age;
};
};
}
var xiaoming = makePerson("Xiaoming", 10);
console.log(xiaoming.name, xiaoming.age); // "Xiaoming" 10
console.log(xiaoming.getIntro()); // "Name:Xiaoming Age:10"
关键字this,使用在函数中时指代的总是当前对象——也就是调用了这个函数的对象。
构造器和new
我们可以使用this和关键字new来对这个构造器进行进一步的封装。
关键字new可以创建一个崭新的空对象,使用这个新对象的this来调用函数,并将这个this作为函数返回值。我们可以在函数中对this进行属性和方法的设置。
这样,我们的函数就是一个可以配合new来使用的真正的构造器了。
通常构造器没有return语句。如果有return语句且返回的是一个对象,则会用这个对象替代this返回。如果是return的是原始值,则会被忽略。
function makePerson(name, age){
this.name = name;
this.age = age;
this.getIntro = function(){
return "Name:" + this.name + " Age:" + this.age;
};
}
var xiaoming = new makePerson("Xiaoming", 10);
console.log(xiaoming.name, xiaoming.age); // "Xiaoming" 10
console.log(xiaoming.getIntro()); // "Name:Xiaoming Age:10"
构造器的prototype属性
上面的实现可以炮制我们想要的自定义对象,但是它和C++中的class比还有一个很大的缺点:每个对象中都包含了重复的函数对象。但是如果我们把这个函数放在外面实现,又会增加不必要的全局函数。
JavaScript提供了一个强大的特性。每个函数对象都有一个prototype属性,指向某一个对象。通过new创建出来的新对象,会将构造器的prototype属性赋值给自己的[[Prototype]]属性。也就是说,每一个通过new 构造器函数生成出来的对象,它的[[Prototype]]都指向构造器函数当前的prototype所指向的对象。
注意,函数的prototype属性和前文所说的隐藏的[[Prototype]]属性并不是一回事。
函数对象的prototype是一个名为“prototype”的普通属性,指向的并不是这个函数对象的原型。函数对象的原型保存在函数对象的[[Prototype]]中。
事实上,每个函数对象都可以看成是通过
new Function()构造出来的,也就是说,每个函数对象的[[Prototype]]属性都由Funtion的prototype属性赋值而来。
我们定义的函数对象,默认的prototype是一个空对象。我们可以通过改变这个空对象的属性,动态地影响到所有以这个对象为原型的对象(也就是从这个函数生成的所有对象)。
于是上面的例子可以改写为:
function makePerson(name, age){
this.name = name;
this.age = age;
}
var xiaoming = new makePerson("Xiaoming", 10);
makePerson.prototype.getIntro = function(){
return "Name:" + this.name + " Age:" + this.age;
};
console.log(xiaoming.name, xiaoming.age); // "Xiaoming" 10
console.log(xiaoming.getIntro()); // "Name:Xiaoming Age:10"
这里是先构造了对象xiaoming,再为它的原型增加了新的方法。可以看到,xiaoming可以通过原型链调用到新定义的原型方法。
需要注意的是,如果直接令函数的prototype为新的对象,将不能影响到之前生成的继承者们——因为它们的[[Prototype]]中保存的是原来的prototype所指向的对象的引用。
四、参考
MDN | 重新介绍JavaScript
MDN | Primitive
原型继承
MDN | 原型与渲染链
JavaScript 类型、原型与继承学习笔记的更多相关文章
- JavaScript权威设计--JavaScript类型,值,变量(简要学习笔记三)
1.负号是一元求反运算 如果直接给数字直接量前面添加负号可以得到他们的负值 2.JavaScript中的运算超出了最大能表示的值不会报错,会显示Infinity. 超出最小也不报错,会显示-I ...
- 【09-23】js原型继承学习笔记
js原型继承学习笔记 function funcA(){ this.a="prototype a"; } var b=new funcA(); b.a="object a ...
- 《JavaScript DOM 编程艺术》 学习笔记
目录 <JavaScript DOM 编程艺术> 学习笔记 第一章 js简史 第二章 js语法 准备工作 语法 第三章 DOM DOM中的D DOM中的O DOM中的M 第四章 js图片库 ...
- 【面试必备】javascript的原型和继承
原型.闭包.作用域等知识可以说是js中面试必考的东西,通过你理解的深度也就能衡量出你基本功是否扎实.今天来复习一下javascript的原型和继承,虽说是老生常谈的话题,但对于这些知识,自己亲手写一遍 ...
- 【转载】Javascript原型继承-学习笔记
阮一峰这篇文章写的很好 http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javas ...
- JavaScript继承学习笔记
JavaScript作为一个面向对象语言(JS是基于对象的),可以实现继承是必不可少的,但是由于本身并没有类的概念,所以不会像真正的面向对象编程语言通过类实现继承,但可以通过其他方法实现继承.(jav ...
- 深入浅出JavaScript之原型链&继承
Javascript语言的继承机制,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instanc ...
- Python 3之str类型、string模块学习笔记
Windows 10家庭中文版,Python 3.6.4, Python 3.7官文: Text Sequence Type — str string — Common string operatio ...
- JavaScript的原型链继承__propt__、prototype、constructor的理解、以及他们之间相互的关系。
回想自己已经工作了有一段时间了,但是自己对JavaScript的原型链.和继承的理解能力没有到位,最近他们彻底的整理并且复习了一遍. 本案例中部分文案来自网络和书籍,如有侵权请联系我,我只是把我的理解 ...
随机推荐
- 【探索之路】机器人篇(5)-Gazebo物理仿真环境搭建_让机器人运动起来
如果完成了前两步,那么其实我们已经可以去连接我们的现实中的机器人了. 但是,做机器人所需要的材料还没有到,所以我们这里先在电脑平台上仿真一下.这里我们用到的就算gazebo物理仿真环境,他能很好的和R ...
- C语言实现的多线程定时器
目录 1. 大致功能介绍 2. API库介绍 3. 一个例子 4. 库文件源码 注意事项 1. 大致功能介绍 实现任务列表,定时器会间隔一段时间遍历列表发现要执行的任务 任务列表中的所有任务并行执行 ...
- mysql事务-简介
mysql事务 问题 概要 storage engine必须支持事务 事务根据隔离级别的不同,不同事务之间有不同的可见性 begin 或者 start transaction, 显式开启事务:comm ...
- UNraid学习随手记:显示主板、CPU传感器温度
话不多说直接开始 首先安装NerdTools 地址: https://raw.githubusercontent.com/dmacias72/unRAID-NerdPack/master/plugin ...
- Java并发编程实战(3)- 互斥锁
我们在这篇文章中主要讨论如何使用互斥锁来解决并发编程中的原子性问题. 目录 概述 互斥锁模型 互斥锁简易模型 互斥锁改进模型 Java世界中的互斥锁 synchronized中的锁和锁对象 synch ...
- 真的,kafka 入门看这一篇准没错!
什么是 Kafka Kafka 是一个分布式流式平台,它有三个关键能力 订阅发布记录流,它类似于企业中的消息队列 或 企业消息传递系统 以容错的方式存储记录流 实时记录流 Kafka 的应用 作为消息 ...
- Oracle数据库基础操作语法
转载自:https://www.cnblogs.com/fallen-seraph/p/10685997.html 一.登录Oracle数据库 首先运行Oracle数据库: 默认的有两个账号: 管理员 ...
- 【JS学习】for-in与for-of
前言:本博客系列为学习后盾人js教程过程中的记录与产出,如果对你有帮助,欢迎关注,点赞,分享.不足之处也欢迎指正,作者会积极思考与改正. 总述: 名称 遍历 适用 for-in 索引 主要建议白能力对 ...
- 面试官:Netty的线程模型可不只是主从多Reactor这么简单
笔者看来Netty的内核主要包括如下图三个部分: 其各个核心模块主要的职责如下: 内存管理 主要提高高效的内存管理,包含内存分配,内存回收. 网通通道 复制网络通信,例如实现对NIO.OIO等底层JA ...
- 如果数据库上的row格式是mixed或者mixed的格式,如何对比两台数据库服务器上的数据是否一致呢
如果数据库上的row格式是mixed或者mixed的格式,如何对比两台数据库服务器上的数据是否一致呢