文章目录
1. 重点提炼
-
面向对象思想
-
对象的创建
-
工厂模式
-
new运算符
-
构造函数
-
原型prototype
-
面相对象和面相过程编程
-
类和对象概念
2. 面向对象编程思想
- **面相过程:**注重解决问题的步骤,分析问题需要的每一步,实现函数依次调用;
- 理解:事物发展的(步骤)顺序
- **面相对象:**是一种程序设计思想。将数据和处理数据的程序封装到对象中;
- 编程思维方法 => 抽离对象 --> 共有特征 --> 类
- 对象 => 特征和行为(属性和方法)
- 面相对象特性: 抽象、 继承、封装、多态
优点:提高代码的复用性及可维护性;
3. 对象
Javascript 是一种基于对象的语言,几乎所有东西都是对象;
对象创建方法:
-
字面量创建
-
new Object()创建
-
Object.create()创建:创建对象的原型;
4. 对象和类
- 对象:具体的某个事物;(如:小明、叮当猫)
- 类:一类事物的抽象;(如:人类、猫类)
类 => 泛指
对象 => 特指(独一无二)
如:类 => 猫类
对象 => 波斯猫、狸猫……
4.1 举例
小明走去餐厅看菜单点餐吃饭
首先抽离对象 => 研究特征和行为(属性和方法)
- 小明对象:
小明.走
小明.看
小明.点餐
小明.吃饭
; - 餐厅对象 :
餐厅.菜单
;
=>
小明.走
–>餐厅
–>餐厅.菜单
–>小明.看
–>小明.点餐
–>小明.吃饭
;
实际直观感觉面向对象会更加复杂,写起来也更加麻烦,那为啥还用面向对象的思维去写代码呢?
如果项目非常庞大,逻辑错综复杂,面向对象会使代码更加优雅,组织结构更加清晰,后期对代码的更新迭代更为方便。
4.2 实例
4.2.1 JS对象的创建方式
let obj = {
name:'张三',
age:20,
}
console.log(obj);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.01
Branch: branch01
commit description:a0.01(JS对象的创建方式——js对象普通创建方式)tag:a0.01
let str = "name";
let obj = {
name:str,
age:20
}
console.log(obj);
![image-20201015113543600](https://cdn.jsdelivr.net/gh/6xiaoDi/blogpic/images/20200903_01/20201015113615.png)
属性名是变量,可以动态生成属性。
let str = "name";
let obj = {
str:'张三',
age:20,
}
console.log(obj);
失败了,发现str:'张三',
, str
自动被解析为字符串。
![image-20201015113920718](https://cdn.jsdelivr.net/gh/6xiaoDi/blogpic/images/20200903_01/20201015113920.png)
let str = "name";
let obj = {
[str]:'张三', //属性(名词)
age:20,
}
console.log(obj);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.02
Branch: branch01
commit description:a0.02(JS对象的创建方式——js对象动态创建属性名)tag:a0.02
4.2.2 构造函数创建
let obj1 = new Object();
obj1.name = "张三";
obj1.age = 20;
console.log(obj1);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.03
Branch: branch01
commit description:a0.03(JS对象的创建方式——构造函数创建)tag:a0.03
4.2.3 Object.create() 创建在原型上
let obj = Object.create({
name:"张三",
age:20
})
console.log(obj);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.04
Branch: branch01
commit description:a0.04(JS对象的创建方式——Object.create() 创建在原型上)tag:a0.04
4.2.4 对象调用
let str = "name";
let obj = {
[str]:'张三', //属性(名词)
age:20,
}
//两种方式对象的调用;
console.log(obj.age);
console.log(obj['age']);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.05
Branch: branch01
commit description:a0.05(JS对象的调用——两种调用方式)tag:a0.05
4.2.4.1 两种对象调用区别
通过变量
的方式调用对象属性
,只能通过中括号
而不能通过.
的方式。
let str = "name";
let obj = {
[str]:'张三', //属性(名词)
age:20,
}
let myname = 'age';
console.log(obj.myname);
console.log(myname);
// 通过变量的方式调用对象属性
console.log(obj[myname]);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.06
Branch: branch01
commit description:a0.06(JS对象的调用——两种对象调用区别)tag:a0.06
4.2.5 行为
let str = "name";
let obj = {
[str]:'张三', //属性(名词)
age:20,
action:function(){
console.log("行为、方法");
}
}
obj.action();
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.07
Branch: branch01
commit description:a0.07(JS对象——添加行为)tag:a0.07
简写方法:
let str = "name";
let obj = {
[str]:'张三', //属性(名词)
age:20,
// 简写形式
action(){
console.log("行为、方法");
}
}
obj.action();
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.08
Branch: branch01
commit description:a0.08(JS对象——简写行为方式)tag:a0.08
总结:三种对象创建方法,两种调用方法。
5. 工厂模式
工厂模式
解决了代码复用
的问题;
普通对象创建模式:
// 100个人对象;
// 单例模式(只有一个对象/实例);
let zhangsan = {
name:"张三",
hobby(){
console.log("喜欢篮球");
}
}
let lisi = {
name:"张三",
hobby(){
console.log("喜欢篮球");
}
}
假如定义100个人(对象),我们就用上了工厂模式
// 代码复用性;
// 工厂模式;
function nvwa(name,hobby){
let obj = {}; //加原料;
obj.name = name; //加工原料
obj.hobby = function(){
console.log(hobby)
}
return obj; //出厂;
}
let zhangsan = nvwa("张三","喜欢篮球");
let lisi = nvwa("李四","喜欢足球");
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.09
Branch: branch01
commit description:a0.09(JS对象——工厂模式创建对象)tag:a0.09
6. new运算符
通过new
来改造工厂模式
new
的特点(new
运算符会做以下五件事情):
-
new
执行函数
-
- 自动创建一个空对象
-
- 把创建空对象指向另外一个对象;
-
- 把创建的对象和
this
绑定;
- 把创建的对象和
-
- 隐式返还
this
- 隐式返还
- 执行函数
function test(){
console.log("test...");
}
test();
new test(); // 加不加括号都可 => new test
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.10
Branch: branch01
commit description:a0.10(new运算符——1.执行函数)tag:a0.10
- 自动创建一个对象,并把创建空对象指向另外一个对象,最终不返回值,则可以隐式返回
this
没有return
,就会隐式进行return
这个this
function nvwa(name,hobby){
//t obj = {}; //加原料;
// {}---this(自动创建的对象是跟this绑定在一起)
this.name = name; //加工原料
this.hobby = function(){
console.log(hobby)
}
// 隐式返回this,无需自己return obj了
}
// 自动创建一个对象
let zhangsan = new nvwa();
console.log(zhangsan);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.11
Branch: branch01
commit description:a0.11(new运算符——2.自动创建一个对象,并把创建空对象指向另外一个对象,最终不返回值,则可以隐式返回this
)tag:a0.11
7. 构造函数
- 构造函数要通过
new
来调用this
指向类 - 约定俗成构造函数首字母大写
- 静态属性及方法
- 静态方法里的
this
; - 扩展功能;
- 静态方法里的
显式return
会覆盖隐式返回this
function nvwa(name,hobby){
let obj = {}; //加原料; {}---this(自动创建的对象是跟this绑定在一起)
this.name = name; //加工原料
this.hobby = function(){
console.log(hobby)
}
return {
name:"张三"
}
}
let zhangsan = new nvwa();
console.log(zhangsan);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.12
Branch: branch01
commit description:a0.12(构造函数——显式return
会覆盖隐式返回this
)tag:a0.12
function nvwa(name,hobby){
this.name = name; //加工原料
this.hobby = function(){
console.log(hobby)
}
}
let zhangsan = new nvwa("张三","喜欢篮球");
console.log(zhangsan);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.13
Branch: branch01
commit description:a0.13(构造函数——基本使用)tag:a0.13
new
实例化 => 对象
let temp;
function nvwa(name,hobby){
this.name = name; //加工原料
this.hobby = function(){
console.log(hobby)
}
temp = this;
}
// new 实例化--》对象;
let zhangsan = new nvwa("张三","喜欢篮球");
let lisi = new nvwa("李四","喜欢篮球");
console.log(zhangsan===temp);
console.log(lisi===temp);
这就验证了实例化对象后隐式返回this
,这里temp
返回最新的this
,并指向lisi
。
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.14
Branch: branch01
commit description:a0.14(构造函数——实例化对象)tag:a0.14
new
将工厂模式
演变成另外一种写法 => 构造函数
(this
指向实例化对象)
每次实例化会新开辟一块内容,所以地址
是不相等的。
构造函数 => new
实例化对象 => 特点
-
约定俗成
首字母
大写; -
它会有自己的
原型
; -
方法
写在原型
上;
常考面试题 => 不同对象的构造函数下的方法地址不同。
// 类和对象;
function Person(name,hobby){
this.name = name;
this.hobby = function(){
console.log(hobby)
}
}
let zhangsan = new Person("张三","喜欢篮球");
let lisi = new Person("李四","喜欢足球");
zhangsan.hobby();
lisi.hobby();
// zhangsan.hobby === lisi.hobby ???
console.log(zhangsan.hobby === lisi.hobby);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.15
Branch: branch01
commit description:a0.15(构造函数——特性 => 不同对象的构造函数下的方法地址不同)tag:a0.15
我们来看一个例子,对象对比:
不仅需要对比内容
,还要对比地址
。实际实例化
的对象都会开辟新地址
,只不过这样会占用内存
。
let obj1 = {
name:"张三"
}
let obj2 = {
name:"张三"
}
console.log(obj1===obj2);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.16
Branch: branch01
commit description:a0.16( 不同对象的地址不同)tag:a0.16
我们还发现一个问题,就hobby
这个行为逻辑是一样的,每次实例化一个对象,相当于复制了多个相同的内容,即复制了一堆同样的方法,会很浪费内存。
因此``js`提供了一个公共空间的东西,叫原型。
7.1 构造函数性能
原型
=> 公共空间存放公共方法
公共空间
,即实例化同一种类的对象共享这块空间。
7.2 构造函数原型
7.2.1 prototype原型
prototype
:它是一个对象。
- 通过
new
实例化出来的对象其属性和行为来自两个部分,一部分来自构造函数
,另一部分来自原型
。 - 当声明一个函数的时候,同时也申明了一个原型 。
原型
本身是一个对象
。- 对象属性方法查找规则;
![prototypeimg](https://cdn.jsdelivr.net/gh/6xiaoDi/blogpic/images/20200903_01/20201014185031.png)
7.2.2 原型构造函数及对象关系
构造函数
会有自身和原型构成,实例化的时候,会把构造函数
的东西和原型
一并放在对象
中。
原型
它不会在内存中重新开辟地址
,所以更节约
它的内存
。
![](https://cdn.jsdelivr.net/gh/6xiaoDi/blogpic/images/20200903_01/20201014185113.png)
// 原型:公共空间; prototype:对象;
function Person(name,hobby){
this.name = name;
}
Person.prototype.hobby = function(){
console.log("喜欢篮球")
}
let zhangsan = new Person("张三","喜欢篮球");
let lisi = new Person("李四","喜欢足球");
console.log(zhangsan.hobby===lisi.hobby);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.17
Branch: branch01
commit description:a0.17( 原型属于公共空间,对象共享该内存)tag:a0.17
看一下原型的this
function Person(name,hobby){
this.name = name;
}
Person.prototype.hobby = function(){
console.log(this);
}
let zhangsan = new Person("张三","喜欢篮球");
let lisi = new Person("李四","喜欢足球");
zhangsan.hobby();
lisi.hobby();
this
还是指向实例化对象,实际构造函数和原型种的this
是同一个东西。
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.18
Branch: branch01
commit description:a0.18( 原型的this指向)tag:a0.18
prototype
固有属性 constructor
指向 => Person
;(这个属性是指针指向构造函数) ,它的作用其实就是辨别原型指向
的问题。
Person.prototype.constructor
=== Person
因此书写原型的时候可能会遇到一种问题:
以上方法是一种方法定义原型,还有另一种方式:
重大问题 => 会覆盖固有属性constructor
Person.prototype = {
hobby:function(){
console.log(this.hobby);
}
}
正确写法:
Person.prototype = {
constructor:Person,
hobby:function(){
console.log(this.hobby);
}
}
因此可以用两种方式定义原型:
Person.prototype = {
constructor:Person,
hobby:function(){
console.log(this.hobby);
}
}
Person.prototype.hobby = function(){
console.log(this);
}
我们再通过一个手写例子类比原型的construct
发生的覆盖问题: => 属性丢失
// myproto -->prototype(类比)
let myproto = {
constructor:"构造函数"
}
// 追加;
myproto.name = "张三";
console.log(myproto);
// 覆盖了;
myproto = {
name: "张三"
}
console.log(myproto);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.19
Branch: branch01
commit description:a0.19( 属性丢失)tag:a0.19
constructor作用:
辨别原型指向
的问题,得到其构造函数
,即看到它是通过谁构造的。
function Person(name,hobby){
this.name = name;
this.hobby = hobby;
}
Person.prototype.hobby = function(){
console.log(this.hobby);
}
let zhangsan = new Person("张三","喜欢篮球");
let lisi = new Person("李四","喜欢足球");
console.log(zhangsan.constructor===Person);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.20
Branch: branch01
commit description:a0.20(constructor作用)tag:a0.20
不过我们可能对constructor
作用理解还是不很清楚,感觉也没什么大的作用。
let str = "fsadfdsa";
console.log(str.constructor===String); //判断类型;
let str = new String("fdsadsa"); // 构造函数创建
console.log(str.constructor===String); //判断类型;
这就明显看出constructor
属性可以判断其类型,能够知道是通过哪个构造器构造的。
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.21
Branch: branch01
commit description:a0.21(constructor作用举例)tag:a0.21
8. 工厂模式对比构造函数
根据constructor
属性可以判断其类型,能够知道是通过哪个构造器构造的,可判断底层一些变量的类型,工厂模式是没有的。
构造函数有原型
(公共空间),节约内存,工厂模式是没有的。
- 但是却没有解决对象识别的问题(没有
construct
属性)。即创建的所有实例都是Object
类型。(不清楚是哪个对象的实例) - 没有原型,占用内存。
8.1 构造函数的好处
-
写起来简单
-
更为节约内存
-
拥有
construct
属性,指向会非常清楚
因此推荐使用构造函数
,而不是使用工厂模式
。
9. 对象原理
class
类型 是function
。
理解上:构造函数
就是类
,但严格意义上不是,这是一个很有争议的话题;
因此将其当作一个语法糖即可,实际类还是函数。
注意:构造函数中方法规范上是需要写在原型上,原因是节约性能。
function Person(name,hobby){
this.name = name;
this.hobby = hobby;
}
Person.prototype = {
constructor:Person,
hobby:function(){
console.log(this.hobby);
}
}
let zhangsan = new Person("张三","喜欢篮球");
console.log(zhangsan);
我们会发现:__proto__
与prototype
感觉不太一样,但实际__proto__
指向其构造函数的prototype
console.log(zhangsan.__proto__===Person.prototype);
我们发现其实两者是一个东西,就是表现形式不太一样而已。
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.22
Branch: branch01
commit description:a0.22(对象原理)tag:a0.22
实际上在类里是书写原型是__proto__
在类外面书写是 prototype
function Person(name,hobby){
this.name = name;
this.hobby = hobby;
this.__proto__ = ...
}
Person.prototype = ...
我们再打印这两人__proto__
,其实也是一样的,返回true
。因为其公共空间是一致的,
console.log(zhangsan.__proto__===lisi.__proto__);
10. 静态成员
统计实例化次数:
function Nvwa(name,hobby){
this.name = name;
this.hobby = hobby;
this.num = 0;
}
Nvwa.prototype.hobby = function(){
console.log(this.hobby);
}
let zhangsan = new Nvwa("张三","喜欢篮球");
zhangsan.num++
let lisi = new Nvwa("李四","喜欢足球");
lisi.num++;
console.log(zhangsan.num);
console.log(lisi.num);
这种方法是统计不出来的。
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.23
Branch: branch01
commit description:a0.23(静态成员——统计实例化次数失败)tag:a0.23
静态属性和方法
属于构造函数Nvwa
,不属于某个对象实例。
function Nvwa(name,hobby){
this.name = name;
this.hobby = hobby;
this.num = 0;
}
Nvwa.prototype.hobby = function(){
console.log(this.hobby);
}
// 统计实例化的次数;
// 静态属性和方法属于构造函数Nvwa;
Nvwa.num = 0;
let zhangsan = new Nvwa("张三","喜欢篮球");
Nvwa.num++;
let lisi = new Nvwa("李四","喜欢足球");
Nvwa.num++;
console.log(zhangsan.num);
console.log(lisi.num);
console.log(Nvwa.num);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.24
Branch: branch01
commit description:a0.24(静态成员——统计实例化次数)tag:a0.24
11. 补充说明
注意原型
与对象属性
重名,会优先执行对象属性
(就近原则
)。
function Person(name,hobby){
this.name = name;
this.hobby = hobby;
}
Person.prototype = {
constructor:Person,
hobby:function(){
console.log(1);
console.log(this);
console.log(this.hobby);
}
}
let zhangsan = new Person("张三","喜欢篮球");
console.log(zhangsan.hobby);
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.25
Branch: branch01
commit description:a0.25(原型
与对象属性
重名)tag:a0.25
function Person(name,hobby){
this.name = name;
this.hobby = this;
}
Person.prototype = {
constructor:Person,
hobby:function(){
console.log(1);
console.log(this);
console.log(this.hobby);
}
}
let zhangsan = new Person("张三","喜欢篮球");
console.log(zhangsan.hobby);
Object.getPrototypeOf(zhangsan).hobby();
Object.getPrototypeOf()
方法返回指定对象的原型(内部[[Prototype]]
属性的值)。
注意:如果不利用实例直接调用方法,原型方法中的this
实际指向的就是构造函数中的Prototype
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.26
Branch: branch01
commit description:a0.26(Object.getPrototypeOf()的使用)tag:a0.26
属性名和方法名重名,用对象实例调用方法会报错。(优先调用属性名) zhangsan.hobby();
function Person(name,hobby){
this.name = name;
this.hobby = this;
}
// 使用哪种方式都一样报错
// Person.prototype = {
// constructor:Person,
// hobby:function(){
// console.log(1);
// console.log(this);
// console.log(this.hobby);
// }
// }
Person.prototype.hobby = function() {
console.log(1);
console.log(this);
console.log(this.hobby);
}
let zhangsan = new Person("张三","喜欢篮球");
console.log(zhangsan.hobby);
zhangsan.hobby();
Object.getPrototypeOf(zhangsan).hobby();
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.27
Branch: branch01
commit description:a0.27(属性名和方法名重名,用对象实例调用方法会报错。zhangsan.hobby();
)tag:a0.27
function Person(name,hobby){
this.name = name;
//this.hobby = this;
}
Person.prototype.hobby = function() {
console.log(1);
console.log(this);
console.log(this.hobby);
}
let zhangsan = new Person("张三","喜欢篮球");
zhangsan.hobby();
Object.getPrototypeOf(zhangsan).hobby();
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.28
Branch: branch01
commit description:a0.28(属性名和方法名不能重名)tag:a0.28
let zhangsan = new Person("张三","喜欢篮球");
zhangsan.hobby();
Object.getPrototypeOf(zhangsan).hobby();
zhangsan.__proto__.hobby();
Object.getPrototypeOf(zhangsan).hobby();
可直接调用原型中的方法,也可以用zhangsan.__proto__.hobby();
,
注意此时的this
不是实例对象而是Person.Prototype
,但是用zhangsan.hobby();
调用,此时的this
是实例对象。因此可以得出结论,如果直接用对象调用方法,this
必然是实例,而直接用对象原型调用,this
指向原型。
参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.29
Branch: branch01
commit description:a0.29(Object.getPrototypeOf(zhangsan).hobby() 和 zhangsan.proto.hobby();)tag:a0.29
(后续待补充)