这篇笔记中有什么:javascript
✔️JavaScript的极简介绍
✔️JavaScript中数据类型的简单梳理
✔️JavaScript中的面向对象原理前端
这篇笔记中没有什么:java
❌JavaScript的具体语法
❌JavaScript经过各类内置对象实现的其余特性编程
最新的标准中,定义了8种数据类型。其中包括:后端
基本数据类型,有些版本也译为原始数据类型。数组
什么是基本类型?看一下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
true
和false
。0
、""
、NaN
、null
、undefined
也会被转换为false
。null
。表示未被声明的值。"object"
。var a; console.log(typeof a); // undefined console.log(typeof a); // "undefined"
Symbol()
函数构造,每一个从该函数返回的symbol值都是惟一的。var sym1 = Symbol("abc"); var sym2 = Symbol("abc"); console.log(sym1 == sym2); // false console.log(sym1 === sym2); // false
接触了一些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引擎去作更好一些,手动建立封装对象可能会致使不少问题。
包装对象做为一种技术上的实现细节,不须要过多关注。可是了解这个原理有助于咱们更好地理解和使用基本数据类型。
Function
对象Array
的长度可变,元素类型任意,所以多是非密集型的。数组索引只能是整数,索引从0开始new
操做符建立对象是一种特殊的数据,能够看作是一组属性的集合。属性能够是数据,也能够是函数(此时称为方法)。每一个属性有一个名称和一个值,能够近似当作是一个键值对。名称一般是字符串,也能够是Symbol
。
var obj = new Object(); // 经过new操做符 var obj = {}; // 经过对象字面量(object literal)
有两种方式来访问对象的属性,一种是经过点操做符,一种是经过中括号。
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值就是该对象。
有些地方会用到引用类型这个概念来指代Object类型。要理解这个说法,就须要理解javascript中变量的访问方式。
基本数据类型的值是按值访问的
引用类型的值是按引用访问的
按值访问意味着值不可变、比较是值与值之间的比较、变量的标识符和值都存放在栈内存中。赋值时,进行的是值的拷贝,赋值操做后,两个变量互相不影响。
按引用访问意味着值可变(Object的属性能够动态的增删改)、比较是引用的比较(两个不一样的空对象是不相等的)、引用类型的值保存在堆内存中,栈内存里保存的是地址。赋值时,进行的是地址值的拷贝,复制操做后两个变量指向同一个对象。经过其中一个变量修改对象属性的话,经过另外一个变量去访问属性,也是已经被改变过的。
Object类型的概念和Lua中的table类型比较类似。变量保存的都是引用,数据组织都是类键值对的形式。table中用原表(metatable)来实现面向对象的概念,Javascript中则是用原型(prototype)。
目前看到的类似点比较多,差别性有待进一步比较。
编程时常常会有重用的需求。咱们但愿可以大规模构建同种结构的对象,有时咱们还但愿可以基于某个已有的对象构建新的对象,只重写或添加部分新的属性。这就须要“类型和继承”的概念。
Javascript中并无class实现,除了基本类型以外只有Object这一种类型。可是咱们能够经过原型继承的方式实现面向对象的需求。
注:ECMAScript6中引入了一套新的关键字用来实现class。可是底层原理仍然是基于原型的。此处先不提。
Javascript中,每一个对象都有一个特殊的隐藏属性[[Prototype]]
,它要么为null
,要么就是对另外一个对象的引用。被引用的对象,称为这个对象的原型对象。
原型对象也有一个本身的[[Prototype]]
,层层向上,直到一个对象的原型对象为null
。
能够很容易地推断出,这是一个链状,或者说树状的关系。null
是没有原型的,是全部原型链的终点。
如前文所说,JavaScript中的Object是属性的集合。原型属性将多个Obeject串连成链。当试图访问一个对象的属性时,会首先在该对象中搜索,若是没有找到,那么会沿着原型链一路搜索上去,直到在某个原型上找到了该属性或者到达了原型链的末尾。Javascript就是经过这种形式,实现了继承。
从原理来看,能够很天然地明白,原型链前端的属性会屏蔽掉后端的同名属性。
函数在JavaScript中是一等公民,函数的继承与和其余属性的继承没有区别。
须要注意的是,在调用一个方法obj.method()
时,即便方法是从obj
的原型中获取的,this
始终引用obj
。方法始终与当前对象一块儿使用。
继承一个对象能够经过原型,那么如何可复用地产生对象呢?
可使用函数来模拟咱们想要的“类”。实现一个相似于构造器的函数,在这个函数中定义并返回咱们想要的对象。这样,每次调用这个函数的时候咱们均可以产生一个同“类”的新对象。
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
,使用在函数中时指代的老是当前对象——也就是调用了这个函数的对象。
咱们可使用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"
上面的实现能够炮制咱们想要的自定义对象,可是它和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
所指向的对象的引用。