[前言]编程语言:

  • 面向过程 POP「Process Oriented Programming」:C语言 编程语言之母
  • 面向对象 OOP「Object Oriented Programming」
  • JAVA 编程语言之父
  • JavaScript
  • PHP/Python/Ruby/Go/C#(ASP.NET)/C++/VB…

    一 面向对象的认识

    了解面向对象编程思想,需要先了解对象、类、实例。万物皆对象,一切都可以看成对象 比如自然界中,我们接触,探索,学习的东西都是对象 什么是类?类别 我们想要研究自然界首先划分为动物类 植物类 微生物类

    1. 动物类有可以划分:【大类里划分小类】
    2. 人类:
    3. +男人类
    4. + 女人类
    5. 低等动物类
    6. 哺乳类
    7. 微生物类

    实例 比如我们想要研究男人类 比如想要了解男人的身体组成 我们得拿具体的人来研究 每个人都是人类中的具体实例 自然界中本身就是按照面向对象的思想来设计的

面向对象是一个非常伟大的编程思想,而JS就是基于这个思想构建出来的一门编程语言,所以在JS中存在:对象、类、实例 三个概念!!

  • @1 对象:JS中一切我们学习和使用的东西都是对象(研究对象),它是一个泛指,“万物皆对象”
  • @2 类:按照特征特点,把事物进行归纳分类「大类(父类)->小类(子类)」;而且类一定会赋予它的每个成员,一些公共的属性和方法;
  • @3 实例:具体的事物,类中的每一个具体的成员;每一个实例都具备一些自己独有的属性和方法(私有的特征),也会具备类赋予他的公共属性和方法(公有的特征)!!

    JS中的内置类:

    ->根据JS中事务的特征,进行类别划分 而默认划分好的类别就是内置类 这些类的父类都是Object[基类]

  • 每一个数据类型值都有一个自己所属的内置类:

    • Number类(每一个数字都是这个类的一个实例)
    • String类、
    • Boolean类、
    • Symbol类、
    • BigInt类、
    • Object 类
      • Object {}普通对象类就是它的实例
      • Array数组 每一个数组都是Array的实例也是Object类的实例
      • RegExp类、正则类
      • Date日期类
      • Error类、
    • Function类… 所有的函数就是这个类的实例

image.png

  • 每一个元素标签都有一个自己所属的内置类,例如:
    • div -> HTMLDivElement -> HTMLElement -> Element -> Node -> EventTarget -> Object
    • p -> HTMLParagraphElement -> HTMLElement …

image.png

  • 每一个类赋予其实例的公共属性方法,在类的原型对象上,例如:Array.prototype

    二 [类]构造函数

    构造函数,字面上的意思理解:

    • “构造”:就是通过某种手段或者方法,创造出来(重构出来);
    • “函数”:就是我们之前学过的函数


    基于构造函数创建自定义类和创建类的相关实例 类专业名词:构造函数/构造器 constructor 自定义类:创建一个函数,执行的时候用“new”来执行,这样这个函数就是类(构造函数),返回的结果是这个类的一个实例对象


为什么要按照类和实例来构建?

好处:公有和私有,实例即能拥有一些自己的私有的特征,也能拥有类赋予他们的公共的特征,这就是私有和公有化划分的非常明显

首先看普通函数执行

function Func(name, age) {
    this.name = name; 
    this.age = age;
}
let f1 = Func('曹雅倩', 18);//=> 把Func函数执行(当做普通函数执行)
//=>方法中的THIS:window
console.log(f1); //=>undefined 因为没有返回值
console.log(window.name, window.age); //=>'曹雅倩'  18

new执行

这是我们的普通函数执行,在上面的代码的基础上我们加个new,一切就都变了

function Func(name, age) {
    this.name = name; //这里的this就是给创建的那个实例对象 赋予私有的属性和方法
    this.age = age;
}
let f1 = new Func('曹雅倩', 18);
console.log(f1); //=> {name: "曹雅倩", age: 18}
console.log(window.name, window.age); //=> window.name是空  window.age是undefined

此时我们很明显发现不一样了,由window.name不在是‘曹雅倩’, window.age不在是‘18’,我们可以断定,函数体中的this绝不在是window了,而且在没有return的情况下f1不在是undefined了;
这种在函数执行前加一个new 的方式,就是我们的构造函数执行;


  • 在ES3语法中:
    • new 函数() => 这种方式就是基于构造函数的方式来执行
    • 约定的语法规范:类名的第一个字母一般都是大写的

就像是这样👇:

function Func(name, age) {
    this.name = name; 
    this.age = age;
}
let f1 = new Func('曹亚倩', 18);

此时:

  1. Func不在被誉为普通函数,而是叫做构造函数(也就是我们所谓的自定义类)
  2. 返回的结果也不再基于RETRUN来判断返回值,返回的结果是当前类的一个实例

    想创建自定义类和创建类的实例,只需要在执行的时候 不在 “函数()“ 普通函数执行; 而是 “new 函数()“ 执行,也就是构造函数执行, 这样方法被称为类,返回结果被称为类的实例;

构造函数语法与普通函数语法的区别

function Fn() {
    this.x = 100;
    this.y = 200;
}
let f1 = new Fn();
console.log(f1);
let f2 = new Fn;
console.log(f2);
  • 普通函数
    • Fn 是函数本身(不是执行)
    • Fn() 函数执行
  • 构造函数
    • new Fn(); 构造函数执行 =>有参数new
    • new Fn; 构造函数执行(这种方式不能给函数传递实参了) =>无参数new

普通函数执行和New执行的区别

1.普通函数执行

私有上下文 EC(FN)
AO(FN)
x===>10
y===>20
sum===>10

  • 1.初始作用链:
  • 2.初始化This:Fn(10, 20) 看前面有.点没有 没有所以是window
  • 3.初始化arguments
  • 4.形参赋值 x=10 y=20
  • 5.变量提升—
  • 6.代码执行

    2. new[构造函数]执行

    私有上下文 EC(FN)
    AO(FN)
    x===>10
    y===>20
    sum===>10
    @1私有上下文产生后进来的第一件事情是创建这个类的实例对象{堆}0x000
    初始作用链:
    初始化This:@2 让this指向创建的实例对象
    形参赋值 x=10 y=20
    变量提升—
    代码执行
    @3 this.xxx.xx都是给当前实例对象设置的私有属性和方法
    @4 如果函数没有写返回值或者返回的是原始值类型的值 默认都是把创建的实例对象返回
    如果自己返回的是对象类型{函数},才是以自己返回的为主

    3.构造函数执行时:实例的相关问题

    • 1、构造函数执行,由于具备普通函数特征,所以在私有上下文中可能会出现一些私有变量,但是这些私有变量和实例没有必然的联系,私有上下文中的THIS才是实例,所以只有写THIS.XXX.XXX的操作,才是给实例设置私有属性;
      • 实例的私有属性和上下文中的私有变量不是一个东西
    • 2、当前类的每一个实例都是单独的一个对象,实例和实例之间是独立的
    • 3、在构造函数的函数体中,基于 THIS.XXX.XXX 给实例设置的属性和方法都是自己私有的,和其它实例中的属性和方法不冲突

      4.构造函数执行时:return 的相关问题

      ```javascript //=========返回原始值类型的情况 function Fn() { this.x = 100; this.y = 200; return 1; } let f1 = new Fn; console.log(f1); //=>{x: 100, y: 200} 仍然返回当前实例

//=======返回对象的情况 function Fn() { this.x = 100; this.y = 200; return { name: ‘xxx’ }; } let f1 = new Fn; console.log(f1); //=>{name:’xxx’} 不再是Fn类的实例

总结👇:

- 1、没有return,默认返回当前类的实例对象(对象数据类型)
- 2、有return,并且返回原始值值,最后的返回结果还是类的实例,不会有影响
- 3、如果返回的是一个对象(含函数),则会把默认返回的实例给覆盖掉,这样我们接收结果就不在是当前类的实例了,而是自己返回的值  => 真实项目中,如果想创建类的实例,则建议大家不要在手动写return了,防止实例被覆盖
<a name="vEgWf"></a>
## 练习题
```javascript
function Fn(x, y) {
    let sum = 10;
    this.total = x + y;
    this.say = function () {
        console.log(`我计算的和是:${this.total}`);
    };
}

/*==>普通函数执行
let res = Fn(10, 20); //=>把Fn函数执行 当做(普通函数执行)
//=>方法中的This:window因为前面没有点
console.log(res); //因为没有返回值 undefined
console.log(window.total, window.say);*///30 funchion(){}

let f1 = new Fn(10, 20); //构造函数执行 Fn类 
console.log(f1);//此时f1={total:30,say:funciton(){}} // 返回值 f1是它的实例
console.log(f1.sum); //undefined
console.log(f1.total); //30 
console.log(window.total, window.say);//undefined undenfined

let f2 = new Fn; //不加小括号也是和上面一样,把它执行了,创在Fn类的一个实例f2,区别是不能传递实参
console.log(f1 === f2); //false 都是单独的实例
console.log(f1.say === f2.say); //false  this.xxx=xxx都是给实例设置的私有属性

2.png

三 检测实例的几种方法

私有属性

  • 对象本身存储的属性

公有属性

  • 基于__proto查找到的属性

属性到底是公有还是私有,需要看谁来讲

function Fn(){
           this.x=20;
           this.y=30;
        }

Fn.prototype.getx=function(){
  console.log("5678");
}
let f=new Fn();
f.getx();
console.log(f.hasOwnProperty('getx'));//false
console.log(Fn.prototype.hasOwnProperty('getx'));//true

image.png

1)hasOwnProperty 检测当前属性是否为实例私有属性

hasOwnProperty 是Object 类原型上的属性,主要是用来检测某个属性是不是某个对象的私有属性。

  • 语法:
    • 对象.hasOwnProperty(“属性名”);
  • 返回值:
    • 如果是私有的返回true,如果不是返回false,
    • 如果没有此属性返回的也是false
    • 公有还是私有,是相对来说的,主要看针对的主体是谁
function fn(){
    this.x=100;
    this.y=100;
  }
  fn.prototype.getX=function(){  //公有的
  }
  var f1=new fn();
  console.log(f1.hasOwnProperty("x")); //true
  console.log(f1.hasOwnProperty("getX")); //false
  console.log(fn.prototype.hasOwnProperty("getX"));//true
  console.log(fn.prototype.hasOwnProperty("getY")); //false 没有此属性

2)in 检测当前属性是否为对象的属性

检测某个某个属性是不是属于某个对象(不管公有还是私有)

  • 语法:属性 in 对象
  • 返回值:只要是对象的属性(不管是公有还是私有的属性)结果都是TRUE
function fn(){
    this.x=100;
    this.y=100;
  }
fn.prototype.getX=function(){
  }
 var f1=new fn();
 console.log("x" in f1); //true  这个是私有的
 console.log("getX" in f1); //true 这个是公有的
 console.log("a" in f1);//false //找不到时会找window window中没有a

3) 某个属性’attr’是否为这个对象’obj’的公有属性

没有检测一个属性是不是公有的,我们自己封装一个

1.这个有小瑕疵

如果一个属性即是公有的又是私有的 ==这个时候的代码就是有问题的

function Fn(x, y) {
    let sum = 10;
    this.total = x + y;
    this.say = function () {
        console.log(`我计算的和是:${this.total}`);
    };
}
let f1 = new Fn(10, 20);
const hasPubProperty = function hasOwnProperty(obj, attr) {
    //attr in obj:attr是obj的一个属性
    //!obj.hasOwnProperty(attr):attr并不是obj的私有属性
    return (attr in obj) && !obj.hasOwnProperty(attr);
};
console.log(hasPubProperty(f1, 'hasOwnProperty'));

2.自己重构Object.prototype.hasPubProperty

思路和具体实现

思路

  • 不论对象的私有属性中是否存在这个属性,只要它公有属性中有这个属性「只要是实例所属类的原型上写的属性都是实例的公有属性,而且一直要到Object.prototype」,则结果是true

具体实现:

  • 首先获取当前实例this(obj)的原型对象@A,看@A中是否存在attr这个属性,存在则直接返回true,说明attr是它的一个公有属性,如果不存在,则找@A的原型对象…直到找到Object.prototype;如果整个过程中都没有找到对应的属性,则说明attr压根不是他的公共属性,返回false即可!!
Object.prototype.hasPubProperty = function hasPubProperty(attr) {
    // this -> obj
    // attr -> 要检测的公有属性
    //   + Object.getPrototypeOf(实例):获取某个实例的原型对象
    let proto = Object.getPrototypeOf(this);
    while (proto) {
        // 如果传递的进来的attr是当前找到的proto原型对象中的一个私有属性,则说明attr是实例的一个公有属性
        if (proto.hasOwnProperty(attr)) return true;
        // 只要还可以找到原型对象,则一直向上找
        proto = Object.getPrototypeOf(proto);
    }
    return false;
};

let obj = {
    name: 'zhufeng',
    toString: 100
};
console.log(obj.hasPubProperty('name'));

综合练习

function Fn(x, y) {
    let sum = 10;
    this.total = x + y;
    this.say = function () {
        console.log(`我计算的和是:${this.total}`);
    };
};
//在构造函数体中,基于this.xx=xx是给当前对象设置私有属性和方法
//Fn是构造函数->"类"  实例->"对象类型"
let f1 = new Fn(10, 20); //构造函数创造了实例对象
let f2 = new Fn;//只是不能传参而已
console.log(f1 === f2); // false 这是两个不同“实例对象”,即两个不同的堆内存空间
console.log(f1.say === f2.say);//false
console.log('say' in f1); //'属性' in 对象:判断是否为相关对象中的属性,不论是公有还是私有  返回true/false
console.log(f1.hasOwnProperty('say')); //对象.hasOwnProperty(属性),检测当前属性是否为对象对的私有属性  返回值为ture/false  同时hasOwnProperty也是一个属性(公有属性)

instanceof:检测当前实例是否属于这个类

instanceof: 检测某一个实例是否属于这个类,可以临时用来检测数据类型,可以区分对象

  • 语法: 值 instanceof类
  • 返回值: 是它的实例返回TRUE,反之返回FALSE
    (当前类的原型只要出现在了实例的原型链上就返回true

    function Fn() {}
    let f1 = new Fn;
    console.log(f1 instanceof Fn); //=>TRUE
    console.log(f1 instanceof Array); //=>FALSE
    
  • 局限:instanceof不适用于基本数据类型检测,要求检测的实例必须是对象数据类型的

    console.log(100 instanceof Number); //=>FALSE
    
  • 应用场景:区分对象中的特殊数据格式,例如数组或者正则

    let arr = [10, 20];
    let reg = /^$/;
    // console.log(typeof arr); //=>"object"  typeof不能具体检测对象类型的细分
    // console.log(typeof reg); //=>"object"
    console.log(arr instanceof Array); //=>TRUE
    console.log(arr instanceof RegExp); //=>FALSE
    

    五 函数类型和对象类型

    1.类 是函数类型的

    2.实例 是对象类型

    虽然大部分实例都是对象类型的值,但原始值类型的值,从本质上讲也是自己所属类的实例;例如:‘1’是Number类的实例,但他属于原始值中的’number’类型; ```javascript / 实例是对象类型的 类(构造函数)是函数类型的 / console.log(typeof Number); //所有的数字都是Number类型的实例,而Number是个类(构造函数),所以返回值为”function” 本身的类型是个函数,代表的确实不同的类 console.log(typeof f1); //“object” 实例都是对象类型的


<a name="ZZKHN"></a>
# 六、创建一个值本身有两种方案:叫字面量方案&&构造函数方案
<a name="r5HAG"></a>
#### 1.对象类型值,基于两种不同方案创造出来的值是没啥太大区别的
<a name="YeS8w"></a>
#### 2.对于原始值来讲,两种方案产生的结果是不同的

- 字面量方案得到的是一个标准的原始值类型值
- 但是构造函数方案产生的是一个“非标准特殊对象[对象类型的值]”,但不论那种方案创造的都是Number类的一个实例
```javascript
let obj1 = {},
    obj2 = new Object();
// console.log(obj,obj2);
// console.log(typeof obj,typeof obj2);
//对象类型值,基于两种不同方案创造出来的值是没有太大区别的

let n1 = 10,
    n2 = new Number(10);
console.log(typeof n1,typeof n2);//“number” "object"

七、装箱和拆箱

装箱:将原始值类型隐式转换为对象类型 拆箱:将对象类型隐式转换为原始值类型

let n1 = 10,
    n2 = new Number(10);
// n1无键值对,但是n2有{有键值对的是可以进行成员访问操作}
console.log(n2.toFixed(2)); //“10.00”
console.log(n1.toFixed(2)); //“10.00”  装箱的过程:浏览器或默认把n1这个原始值类型,隐式转换为new Number(n1)这种对象类型,然后再去调用toFixed方法

console.log(n1 + 10); //20
console.log(n2 + 10); //20  
// 拆箱过程:
// @1 n2[Symbol.toPrimitive] -> undefined
// @2 n2.valueOf() -> 10 */