1.js面向对象使用与原理深入浅出

1. 重点提炼

  • 面向对象思想

  • 对象的创建

  • 工厂模式

  • new运算符

  • 构造函数

  • 原型prototype

  • 面相对象和面相过程编程

  • 类和对象概念


2. 面向对象编程思想

  1. **面相过程:**注重解决问题的步骤,分析问题需要的每一步,实现函数依次调用;
    • 理解:事物发展的(步骤)顺序
  2. **面相对象:**是一种程序设计思想。将数据和处理数据的程序封装到对象中;
    • 编程思维方法 => 抽离对象 --> 共有特征 --> 类
    • 对象 => 特征和行为(属性和方法)
  3. 面相对象特性抽象、 继承、封装、多态

优点:提高代码的复用性及可维护性


3. 对象

Javascript 是一种基于对象的语言,几乎所有东西都是对象

对象创建方法

  • 字面量创建

  • new Object()创建

  • Object.create()创建:创建对象的原型;


4. 对象和类

  1. 对象:具体的某个事物;(如:小明、叮当猫)
  2. :一类事物的抽象;(如:人类、猫类)

=> 泛指

对象 => 特指(独一无二)

如: => 猫类

对象 => 波斯猫、狸猫……


4.1 举例

小明走去餐厅看菜单点餐吃饭

首先抽离对象 => 研究特征和行为属性方法

  1. 小明对象:小明.走 小明.看 小明.点餐 小明.吃饭
  2. 餐厅对象 :餐厅.菜单

=>

小明.走–>餐厅–>餐厅.菜单–>小明.看–>小明.点餐–>小明.吃饭;

实际直观感觉面向对象会更加复杂,写起来也更加麻烦,那为啥还用面向对象的思维去写代码呢?

如果项目非常庞大,逻辑错综复杂,面向对象会使代码更加优雅,组织结构更加清晰,后期对代码的更新迭代更为方便。


4.2 实例

4.2.1 JS对象的创建方式

let obj = {
    name:'张三',
    age:20,
}
console.log(obj);

image-20200503154039689

参考: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

属性名是变量,可以动态生成属性。

let str = "name";
let obj = {
    str:'张三',
    age:20,
}
console.log(obj);

失败了,发现str:'张三',str自动被解析为字符串。

image-20201015113920718
let str = "name";
let obj = {
    [str]:'张三',  //属性(名词)
    age:20,
}
console.log(obj);

image-20200503154039689

参考: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);

image-20200503154215110

参考: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);

image-20200503154631998

参考: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']);

image-20200503155018621

参考: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]);

image-20200503160701047

参考: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();

image-20200503161910857

参考: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();

image-20200503161910857

参考: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运算符会做以下五件事情):

    1. new执行函数
    1. 自动创建一个空对象
    1. 把创建空对象指向另外一个对象;
    1. 把创建的对象和this绑定;
    1. 隐式返还this

  1. 执行函数
function test(){
    console.log("test...");
}
test();
new test(); // 加不加括号都可  =>   new test

image-20200503222508554

参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.10
Branch: branch01
commit description:a0.10(new运算符——1.执行函数)

tag:a0.10


  1. 自动创建一个对象,并把创建空对象指向另外一个对象,最终不返回值,则可以隐式返回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);

image-20200503225202449

参考: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);

image-20200503225351025

参考: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);

image-20200503225827080

参考: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

image-20201015123225876

参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.14
Branch: branch01
commit description:a0.14(构造函数——实例化对象)

tag:a0.14


new工厂模式演变成另外一种写法 => 构造函数this指向实例化对象)

每次实例化会新开辟一块内容,所以地址是不相等的。

构造函数 => new实例化对象 => 特点

  1. 约定俗成 首字母大写;

  2. 它会有自己的原型

  3. 方法写在原型上;


常考面试题 => 不同对象的构造函数下的方法地址不同。

// 类和对象;
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);

image-20200503233318144

参考: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);

image-20200503233632159

参考: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

7.2.2 原型构造函数及对象关系

构造函数会有自身和原型构成,实例化的时候,会把构造函数的东西和原型一并放在对象中。

原型它不会在内存中重新开辟地址,所以更节约它的内存


// 原型:公共空间;  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);

image-20200503235215589

参考: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是同一个东西。

image-20200504004529091

参考: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);

image-20200504103650396

参考: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);

image-20200504111440667

参考: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属性可以判断其类型,能够知道是通过哪个构造器构造的。

image-20200504111440667

参考:https://github.com/6xiaoDi/Native-JavaScript-OOP/tree/a0.21
Branch: branch01
commit description:a0.21(constructor作用举例)

tag:a0.21


8. 工厂模式对比构造函数

根据constructor属性可以判断其类型,能够知道是通过哪个构造器构造的,可判断底层一些变量的类型,工厂模式是没有的。

构造函数有原型(公共空间),节约内存,工厂模式是没有的。

  1. 但是却没有解决对象识别的问题(没有construct属性)。即创建的所有实例都是Object类型。(不清楚是哪个对象的实例)
  2. 没有原型,占用内存。

8.1 构造函数的好处

  1. 写起来简单

  2. 更为节约内存

  3. 拥有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

image-20200504113429426


console.log(zhangsan.__proto__===Person.prototype);

我们发现其实两者是一个东西,就是表现形式不太一样而已。

image-20200504111440667

参考: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);

这种方法是统计不出来的。

image-20200504115902698

参考: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);

image-20201017230417243

参考: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);

image-20200504133530762

参考: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

image-20200504133901089

参考: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();

image-20201017232031005

参考: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();

image-20201016120811628

参考: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指向原型。

image-20201016121256086

参考: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




(后续待补充)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值