首先简单的复习一下编程语言的分类
[前言]编程语言
-
面向过程 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.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(); 构造函数执行 =>有参数new
-
new Fn; 构造函数执行(这种方式不能给函数传递实参了) =>无参数new
-
普通函数执行和New执行的区别
1.普通函数执行
私有上下文 EC(FN)
AO(FN)
x===>10
y===>20
sum===>10
- 1.初始作用链:<EC(FN),EC(G)>
- 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
初始作用链:<EC(FN),EC(G)>
初始化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 的相关问题
//=========返回原始值类型的情况
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了,防止实例被覆盖
练习题
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查找到的属性
属性到底是公有还是私有,需要看谁来讲
| 用途 | 语法 | 返回值 | |
|---|---|---|---|
| hasOwnProperty | 当前属性是否为实例私有属性 | 对象.hasOwnProperty("属性名") | 私有:true 不是/没有:false |
| in | 某个属性是不是属于某个对象 | 属性 in 对象 | 对象的属性:TRUE |
| attr | 某个属性'attr'是否为这个对象'obj'的公有属性 | ||
| instanceof | 检测当前实例是否属于这个类 | 值 instanceof类 | true/false |
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(当前类的原型只要出现在了实例的原型链上就返回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'类型;
/*
实例是对象类型的
类(构造函数)是函数类型的
*/
console.log(typeof Number); //所有的数字都是Number类型的实例,而Number是个类(构造函数),所以返回值为"function" 本身的类型是个函数,代表的确实不同的类
console.log(typeof f1); //"object" 实例都是对象类型的
五、创建一个值本身有两种方案:叫字面量方案&&构造函数方案
1.对象类型值,基于两种不同方案创造出来的值是没啥太大区别的
2.对于原始值来讲,两种方案产生的结果是不同的
- 字面量方案得到的是一个标准的原始值类型值
- 但是构造函数方案产生的是一个“非标准特殊对象[对象类型的值]”,但不论那种方案创造的都是Number类的一个实例
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
总结