什么是类
类的实质是一种数据类型,类似于int、char等基本类型,不同的是它是一种复杂的数据类型。因为它的本质是类型,而不是数据,所以不存在于内存中,不能被直接操作,只有被实例化为对象时,才会变得可操作。
类是对现实生活中一类具有共同特征的事物的抽象。如果一个程序里提供的类型与应用中的概念有直接的对应,这个程序就会更容易理解,也更容易修改。
一组经过很好选择的用户定义的类会使程序更简洁。此外,它还能使各种形式的代码分析更容易进行。
ES6之前通过构造函数来实现class类
构造函数的特点:
- 构造函数使用大驼峰命名规则(首字母大写),用来区分于普通函数
- 内部使用的this对象,来指向即将要生成的实例对象
- 使用 new 操作符来生成实例对象
所有的实例对象都可以继承构造器函数中的属性和方法。但是,同一个对象实例之间,无
法共享属性。
来看例子:
function Person(name, age, sex, health) {this.name = name;this.age = age;this.sex = sex;this.health = health;this.temper = function () {this.health++;}this.smoke = function () {this.health--;}}let p = new Person('超', 20, '男', 100)p.console.log(p)
打印结果:
![35212a6c9b6dab9dc1433b1796aa645f.png](https://i-blog.csdnimg.cn/blog_migrate/0d4c508cd6209e8dfb65bf5233ebddf1.jpeg)
我们可以通过调用p对象里的方法来改变对象的属性值:
![e7343f5302042e8b74b0bc5f6d7722c7.png](https://i-blog.csdnimg.cn/blog_migrate/2e102f5c14cc0dbf16321c729950f13b.jpeg)
一个构造函数可以构造出多个对象,对象之间互不影响(对象a发生变化,对象b不会跟着改变)
eg:
![c5749696abfbf1f540d6380ba3aa1721.png](https://i-blog.csdnimg.cn/blog_migrate/6e74e32b3c5e56ffa2aa1399b0386e40.jpeg)
ps:通过参数给构造函数传参,才能使一个函数打出不同的效果(好比一个人,假设刚出生时差不过,但是经过后天的变化出现不一样的结局,比如我不爱学习天天打游戏,学习越来越差,你们热爱学习努力奋斗终于考上了985)
构造函数能构造出函数的前提是前面必须得加 new 有了new之后,这个函数就彻底发生变化!
变化1:在函数内部的最顶端加上这样一行代码(隐式的,你看不见的那种):
let this = {}; 声明一个this变量,给它赋值一个空对象.
变化2:在函数内部的最低端 return this;
嗯,我通过一个普通函数来模拟一个来帮助大家理解吧:
ps: 因为this是关键字,所以我用this来代替function person(name, age, sex) {let that = {}that.name = name;that.age = age;that.sex = sex;that.setName = function(changeName) {return that.name = changeName}return that;}let p = person('超', 20, '男')console.log(p)p.setName('大帅哥')console.log(p)
打印结果:
![da7854a3166466d97c493e4e2e08bdfb.png](https://i-blog.csdnimg.cn/blog_migrate/5ad3fe6d62c32877a51aa933c1aff4b9.jpeg)
当然,这只是模拟的,它里面远远不止这些。
既然我们知道了 new 一个函数,它会做哪些事情,如果我们破坏它会怎么样呢?
没被破坏前:
![5be6f7d13433f4bdb4179c0cedb69351.png](https://i-blog.csdnimg.cn/blog_migrate/8167743cfdc2f1236ceb5329f65625c0.jpeg)
破坏之后:
![56244c7c1d05e6453e05c731893bc0b1.png](https://i-blog.csdnimg.cn/blog_migrate/8ff1093647a24b58993d4fdbde5505f6.jpeg)
ps: 如果我们要破坏它,只可以让它返回引用类型才可以成功破坏,在构造函数里return原始值是没有效果的:
![76c0eeb69b94b7083098a7e43cfedb80.png](https://i-blog.csdnimg.cn/blog_migrate/51995d8062eb63348cb4105e59851597.jpeg)
不过我觉得应该没什么太大作用,应该没人考这个,大家忘了吧。。。
包装类
铺垫:原始值是不能有属性和方法的,属性和方法只有对象才有,原始值只是一个值作为自己独立的个体存在。
在JavaScript中,数值类型,字符串类型其实是分为两个,只有原始值数字才是原始值,只有原始值字符串才是原始值。
证实:
两种声明数字原始值:
let num = 123;let num_2 = new Number(123)console.log(num, num_2) // 123 Number {[[PrimitiveValue]]: 123}
![9b81eec43e885c8b2e6884c627e9bb1d.png](https://i-blog.csdnimg.cn/blog_migrate/ed8090b099f9e14adb414dc1eb1b884e.jpeg)
大家需要知道的就是第二种它也是数字,它也可以进行数字运算,同时它也可以有自己的属性和方法:
![8fe285949b93b91d55165b6fb4e82e8c.png](https://i-blog.csdnimg.cn/blog_migrate/63601110bb862575682cbd60c85cc6c3.jpeg)
不过当它进行数字运算之后,它就变成了真正的原始值。
![0eb9e38d89e80ddb9142f90210e68d30.png](https://i-blog.csdnimg.cn/blog_migrate/4e4ec0171d481da1d41a4cf673891c13.jpeg)
ps: 这种叫做数字类型对象。
字符串类型也是同样如此:
![0309e8079fb55128364a5f17ed1030d5.png](https://i-blog.csdnimg.cn/blog_migrate/d88ea098a137a32c1e6c3795bc47b534.jpeg)
ps: 没错,这是叫做字符串类型对象。
其实布尔类型也可分为两种,大家可以试下。
好啦!回到正题,前面我们说了,原始值是不可能有属性和方法的,可是我们知道,当我们想知道某一个字符串长度时我们会调用.length ,当我们想把字符串转为数组时,我们可以调用.split(',') 等等,既然原始值是没有属性和方法的,那么为什么我们可以调用呢?这就是待会要说的包装类了。
原始数据类型在调用属性方法时,经历了一个过程,叫做包装类。
- 在原始值调用属性或方法(无论是赋值还是查找)时,它会隐式的发生一个过程。
- 创建出一个和基本类型值相同的对象
- 这个对象就可以调用包装对象下的方法,并且返回结果
- 之后这个临时创建的对象就被销毁了
eg:
let str = '我是一个字符串'let strLen = str.length// 当我们进行上述操作时,系统会隐式发生以下操作// 1.创建出一个和基本类型值相同的对象// let strObj = new String('我是一个字符串')// 2.调用这个字符串对象下的length方法,并且返回结果给strLen变量.// let strLen = strObj.length// 3.销毁这个字符串对象console.log(strLen) // 7
比较和引用类型的区别:
![a839bd38fe03f36c16875fb399353b8f.png](https://i-blog.csdnimg.cn/blog_migrate/302191b85e0d8f528f35ee456ec14d54.jpeg)
可以看出,引用类型的属性和方法在可以访问的同时,还可以重新赋值,当给例子中的arr的length属性重新赋值为5时,它的长度发生了截取,变为5位。
那么如果是原始数据类型呢?
let str = 'hello world'str.length = 5console.log(str) // hello world
为什么字符串没有发生截取呢?
这是因为在发生了隐式的字符串对象操作之后就顺便给它销毁了。
正是因为包装类的存在,当我们在使用字符串等原始值调用属性和方法时会产生原始数据类型也有属性和方法。
原型
原型是function对象的一个属性,它定义了构造函数制造出对象的公有祖先,通过构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象!
大致意思就是说,原型是一个对象,我们可以给这个对象添加属性和方法,这样的话,只要是这个构造函数构造出来的对象都可以调用该构造函数中原型的属性和方法!
我们可以打印出来一个看看
![3b4b67db99a5995ce4bec338cf729ae6.png](https://i-blog.csdnimg.cn/blog_migrate/1665604fbe3b52520e5fad24e3376689.jpeg)
我们可以看到它现在是一个空对象(先这么认为),既然是对象,我们就可以给它添加属性和方法!
![43804dfa71a2d882c29ae3dfa22ad943.png](https://i-blog.csdnimg.cn/blog_migrate/b59e172bc64820cea2b9d777a9a4d6db.jpeg)
给构造函数的原型添加方法,只要是该构造函数构造出来的对象都可以继承到。
![da8e49837802338512eaab95d330427d.png](https://i-blog.csdnimg.cn/blog_migrate/52b5edc9832e33e0cf3c51e132301ffe.jpeg)
原型的特点
1,如果对象的自己身上的属性或方法和原型的属性和方法名一致,优先执行自己身上的(可以参考作用域链)
eg:
![1abe28bce24c54335f34eea4ba4f5c87.png](https://i-blog.csdnimg.cn/blog_migrate/3d39ec502d4e81fcff11892727360930.jpeg)
构造函数构造出的对象不能直接操作原型上的属性和方法(因为你这样做是给自身赋值调用之类的)。
![a9eaeb262d8ac76226963ad13c73b459.png](https://i-blog.csdnimg.cn/blog_migrate/e818a6f7bc8a98fb16d2fe0092078b0d.jpeg)
不过可以通过隐式的属性__proto__来修改
![d83d3d4892d38665ada40b1785ab2c81.png](https://i-blog.csdnimg.cn/blog_migrate/f74dda1e9fe074d098ee46addf37a358.jpeg)
那么为什么可以通过__proto__就可以实现修改了呢?
![c407f61abda1c6a1d5530a94b7f0d0dd.png](https://i-blog.csdnimg.cn/blog_migrate/3e65cb8abc9fe96c53bad11178d42b34.jpeg)
这是因为__proto__这个隐式属性指向的正是这个构造函数的原型。
![c0cdce9261ac58c5e1d5305ea0d02e7b.png](https://i-blog.csdnimg.cn/blog_migrate/ee63d986eee4b50b7fe35b7ded0d620f.jpeg)
我们分别打印构造函数的原型和构造函数构造出对象的__proto__,可以看出它两是完全一样的。所以我们可以得出一个结论:对象如果要查找它的原型,可以通过.__proto__属性来查找。
那么问题来了,既然我们可以通过.proto来查看构造函数的原型,那么我们如果想查看构造函数呢?
之前我们说过,对象的原型默认是一个空对象(暂且怎么理解),现在来解释一下为什么什么说,其实它并不是一个纯空的,在它里面有一个隐式的属性:constructor
![0fe5da42b2c607362d14c56c761b3d5c.png](https://i-blog.csdnimg.cn/blog_migrate/91aeb0809e773b8f5bf37afe81229c36.jpeg)
我们通过访问这个属性可以获取该构造函数
![40796d163b403c6d9d5c2c80d4291d4f.png](https://i-blog.csdnimg.cn/blog_migrate/70f458c9337b3e75c08afe3bd1e12591.jpeg)
原型链
在之前讲过,对象的隐式属性__proto__指向构造这个对象的构造函数的原型。但是我们点开这个构造函数的原型中的属性,你会发现原型中还有一个隐式的属性__proto__,而我们都知道__proto__是指向原型的,也就是说原型中还有原型。利用这一特性,我们就可以形成原型链。
![dd76401fec9a4bd5b4b5f968f87bba09.png](https://i-blog.csdnimg.cn/blog_migrate/11d4aa482280c45e2607d8d6601b437c.jpeg)
什么是原型链,类似于作用域链(作用域链解答:作用域是针对变量的,比如我们创建了一个函数,函数里面又包含了一个函数,那么现在就有三个作用域,这时候假设我们在最内层的函数里访问window全局对象中的变量,它会先看自身有没有这个变量,如果没有,那么它会找到它外层的函数,再没有就找到了window全局对象中,我们将这种叫做作用域链,这种行为可以叫做沿着作用域链查找;作用域的特点就是,先在自己的变量范围中查找,如果找不到,就会沿着作用域往上找。)原型链也是这样,比如该构造函数要访问祖先身上的属性,它会先看看自己身上有没有,如果没有会依次往上查找。知道知道最上层为止。
eg:
![208900a3bff72dbd9a78d83c5c216880.png](https://i-blog.csdnimg.cn/blog_migrate/7e7f9a156a4134917c86ccb9bd1474db.jpeg)
当如果自身或上一级有时:
![f0cdbb4da59d790db7cf3c7bc19a59a5.png](https://i-blog.csdnimg.cn/blog_migrate/3d697e09879e9e5de4765b39877596ea.jpeg)
就和css中的继承一样,如果自身没有定义就会继承父元素的样式。