[前言]编程语言:
- 面向过程 POP「Process Oriented Programming」:C语言 编程语言之母
- 面向对象 OOP「Object Oriented Programming」
- JAVA 编程语言之父
- JavaScript
- PHP/Python/Ruby/Go/C#(ASP.NET)/C++/VB…
一 面向对象的认识
了解面向对象编程思想,需要先了解对象、类、实例。万物皆对象,一切都可以看成对象 比如自然界中,我们接触,探索,学习的东西都是对象 什么是类?类别 我们想要研究自然界首先划分为动物类 植物类 微生物类
动物类有可以划分:【大类里划分小类】
人类:
+男人类
+ 女人类
低等动物类
哺乳类
微生物类
实例 比如我们想要研究男人类 比如想要了解男人的身体组成 我们得拿具体的人来研究 每个人都是人类中的具体实例 自然界中本身就是按照面向对象的思想来设计的
面向对象是一个非常伟大的编程思想,而JS就是基于这个思想构建出来的一门编程语言,所以在JS中存在:对象、类、实例 三个概念!!
- @1 对象:JS中一切我们学习和使用的东西都是对象(研究对象),它是一个泛指,“万物皆对象”
- @2 类:按照特征特点,把事物进行归纳分类「大类(父类)->小类(子类)」;而且类一定会赋予它的每个成员,一些公共的属性和方法;
@3 实例:具体的事物,类中的每一个具体的成员;每一个实例都具备一些自己独有的属性和方法(私有的特征),也会具备类赋予他的公共属性和方法(公有的特征)!!
JS中的内置类:
->根据JS中事务的特征,进行类别划分 而默认划分好的类别就是内置类 这些类的父类都是Object[基类]
每一个数据类型值都有一个自己所属的内置类:
- Number类(每一个数字都是这个类的一个实例)
- String类、
- Boolean类、
- Symbol类、
- BigInt类、
- Object 类
- Object {}普通对象类就是它的实例
- Array数组 每一个数组都是Array的实例也是Object类的实例
- RegExp类、正则类
- Date日期类
- Error类、
- Function类… 所有的函数就是这个类的实例
- 每一个元素标签都有一个自己所属的内置类,例如:
- div -> HTMLDivElement -> HTMLElement -> Element -> Node -> EventTarget -> Object
- p -> HTMLParagraphElement -> HTMLElement …
- 每一个类赋予其实例的公共属性方法,在类的原型对象上,例如: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);
此时:
- Func不在被誉为普通函数,而是叫做构造函数(也就是我们所谓的自定义类)
- 返回的结果也不再基于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()
; 构造函数执行 =>有参数newnew 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.变量提升—
-
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} 仍然返回当前实例
- 1、构造函数执行,由于具备普通函数特征,所以在私有上下文中可能会出现一些私有变量,但是这些私有变量和实例没有必然的联系,私有上下文中的THIS才是实例,所以只有写THIS.XXX.XXX的操作,才是给实例设置私有属性;
//=======返回对象的情况 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都是给实例设置的私有属性
三 检测实例的几种方法
私有属性
- 对象本身存储的属性
公有属性
- 基于__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
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
(当前类的原型只要出现在了实例的原型链上就返回truefunction 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 */