欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 手游 > day11JS-面向对象和js中的设计模式

day11JS-面向对象和js中的设计模式

2025/8/10 4:29:23 来源:https://blog.csdn.net/kwj258036/article/details/141326363  浏览:    关键词:day11JS-面向对象和js中的设计模式

1. 面向对象

1.1 什么是面向对象

        面向对象是一种编程思想,JS就是基于这个思想构建出来的一门编程语言,所以JS中存在对象、类、实例的概念。

         对象:万物皆对象。

        构造函数(类):把具有某一特征的内容可以划分为一类,类的身上会具有某些属性和方法(静态属性和方法)

使用静态属性和方法的语法

类名(构造函数名).属性名

类名(构造函数名).方法名

<body><script>let arr = new Array();console.dir(Array);console.dir(Array.name);//Arrayconsole.dir(Array.isArray([10, 20]));//true</script>
</body>

         实例对象具体的事物,属于某类中的具体成员每个成员(实例对象)都有自己私有的属性和方法,也有公有的属性和方法

<body><script>let arr = new Array([10, 100]);console.log(arr);</script>
</body>

1.2  JS中的内置类

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

        Number类(每一个数字都是他的实例)、String类、Boolean类、Symbol类、BigInt类、Object类、Array类、RegExp类、Date类、Error类、Function类...

2.每一个元素标签都有一个自己所属的内置类,例如:

        div --> HTMLDivElement --> HTMLElement --> Element --> Node --> EventTarget --> Object -->null 

 其它元素标签同理。

3.每一个类赋予其实例对象的公共属性方法,在类的原型对象上。

var num = new Number(10);
console.log(num)

原型链:num -->Number --> Object -->null

实例对象的私有属性和方法[[PrimitiveValue]]:10

实例对象的公有属性和方法 Number.prototype

2. 原型和原型链

2.1  原型

1. 几乎所有的函数都有一个prototype(原型)属性指向自己所属类的原型。

注意!!!

箭头函数没有prototype属性

es6的快捷函数也没有prototype属性

2. prototype这个原型对象天生自带一个constructor(构造函数)属性,指的是自己的构造函数prototype主要给实例对象提供公有的方法。

3. 所有的对象都天生自带一个_ _proto_ _(原型链)属性,它指向实例所属类的原型

4. 静态的属性和方法放在构造函数的键值对里面。

5.Object 所有对象类型“基类”

【函数】:普通函数(实名或匿名)、构造函数、生成器函数、箭头函数(不具备prototype)、基于ES6给对象某个成员赋值函数值的快捷操作(不具备prototype)。

【对象】:普通的对象、数组、实例对象prototype(原型对象)等。

<body><script>function Person(name, age) {//私有属性和方法this.name = name;this.age = age;this.show = function () {console.log("show");}}//公有属性和方法Person.prototype.job = "学生";Person.prototype.fn = function () {console.log("fn");};//静态属性和方法Person.house = "别墅";Person.f = function () {console.log("f")}//实例对象let p = new Person("lili", 18);</script>
</body>

原型链: 

2.2 原型链查找机制

        当我们要查找或者操作实例上某个方法或者属性的时候,我们会先查找实例的私有属性,看看私有上是否有,如果有,停止查找;

        如果没有,就会基于_ _proto_ _向上查找,如果找到,就是公有属性

        如果还没有,继续基于_ _proto_ _原型链向上查找,直到Object基类,如果都没有,就是操作方法或者属性不存在

2.3 案列

<body><script>function Fn() {this.x = 100;this.y = 200;this.getX = function () {console.log(this.x);}}Fn.prototype.getX = function () {console.log(this.x);};Fn.prototype.getY = function () {console.log(this.y);};let f1 = new Fn;let f2 = new Fn;console.log(f1.getX === f2.getX); //falseconsole.log(f1.getY === f2.getY); //trueconsole.log(f1.__proto__.getY === Fn.prototype.getY); //trueconsole.log(f1.__proto__.getX === f2.getX); //falseconsole.log(f1.getX === Fn.prototype.getX); //falseconsole.log(f1.constructor); //Fnconsole.log(Fn.prototype.__proto__.constructor); //Object</script>
</body>

2.4 在原型上拓展方法

<body><script>let arr = [100, 200, 345];let res = arr.push(123);//原型的方法console.log(res);//返回数组的长度。//在原型上拓展方法的写法:Array.prototype.myPush = function myPush() {//1.this是谁调用push就指向谁//2.参数使用arguments来接for (let i = 0; i < arguments.length; i++) {this[this.length] = arguments[i];}return this.length;}let arr1 = [1, 2, 3, 4];let arr2 = arr1.myPush(345, 234);console.log(arr1);//[1,2,3,4,345,234]console.log(arr2);//6</script>
</body>

2.5 链式调用

想要实现一个需求:

var ary=[5,8,3,1] ;想要让这个数组先排序,然后再倒序,然后再往里面添加一个10,然后再删除第一项。

<body><script>let arr = [5, 8, 3, 1];//原数组:排序后的新数组arr.sort();//原数组:倒叙后的新数组arr.reverse();//原数组:新增后的新数组的长度arr.push(10);//原数组:被删除后的新数组arr.shift();console.log(arr);</script>
</body>

实现链式写法的要求:上一步的返回值必须是同一种类型,才可以继续链式操作

<body><script>let arr = [5, 8, 3, 1];//原数组:排序后的新数组//原数组:倒叙后的新数组//原数组:新增后的新数组的长度//到shift()就会报错,因为上一步返回的是长度,和push(10)和shift()的返回值不是同一类型arr.sort().reverse().push(10).shift();//报错</script>
</body>

2.6 instanceof (判断实例属于哪个类)

instanceof 判断 某个实例 是否属于那个类(构造函数)。

<body><script>let arr = [1, 2, 3, 4];console.log(arr instanceof Array);//trueconsole.log(arr instanceof Number);//falseconsole.log(arr instanceof Object);//truefunction Fn(name, age) {this.name = 100;this.age = 200;}Fn.prototype.show = function () {console.log(this.name);}let f = new Fn();//f---Fn.prototype---Object.prototypeconsole.log(f instanceof Fn);//trueconsole.log(f instanceof Object);//true</script>
</body>

2.7 in (判断对象是否有某个属性(公有+私有))

in 检测当前对象是否存在某个属性不论是共有属性,还是私有属性,只要是对象的属性,通过in进行检测返回值都是true。

是自己身上的属性的情况:

1. 本身就是自己身上的属性

2. 原型链查找机制也是属于自己身上的属性。

注意!!!

坑点一 : name没有加引号,所以是一个变量单纯输出name不会报错,因为构造函数有一个全局的name属性,所以单纯输出name“”空引

坑点二: 构造函数身上的键值对里面存放着一个name属性。所以当判断name属性是否属于构造函数,它是属于的(true),这时的name指的是构造函数的静态属性

<body><script>function Fn(name, age) {this.name = name;this.age = age;}Fn.prototype.show = function () {console.log(this.name);}//1. 判断是否属于自己身上的属性let f = new Fn("lili", 18);console.log("age" in Fn);//false Fn是构造函数,它身上的属性是静态属性。console.log("age" in f);//true  f是实例对象console.log("age" in Fn.prototype);//false Fn.prototype是原型对象console.log("show" in Fn.prototype);//true//坑点一:这里的name没有加引号,所以是一个变量//重点:单纯输出name不会报错,因为构造函数有一个全局的name属性,所以单纯输出name是“”空引console.log(name);//""console.log(name in Fn);//falseconsole.log(name in f);//falseconsole.log(name in Fn.prototype);//false//坑点二:构造函数身上的键值对里面存放着一个name属性。console.log("name");//"name"console.log("name" in Fn);//true Fn是构造函数,它身上的属性是静态属性。console.log("name" in f);//true  f是实例对象console.log("name" in Fn.prototype);//false Fn.prototype是原型对象//2.原型链查找机制也是属于自己身上的属性console.log(f);//原型链: f -- Fn.prototype -- Object.prototypeconsole.log("toString" in Object.prototype);//trueconsole.log("toString" in f);//true</script>
</body>

2.8 hasOwnProperty方法(判断对象是否有某个私有属性)

hasOwnPropertyObject 类原型(Object.prototype)上的方法,主要是用来检测某个属性是不是某个对象私有属性

  • 如果是私有返回true,如果不是返回false
  • 如果没有此属性返回的也是false
  • 公有还是私有,是相对来说的,主要看针对的主体是谁。

语法:对象.hasOwnProperty("属性名");


<body><script>function Fn(name, age) {//私有this.name = name;this.age = age;}//公有Fn.prototype.show = function show() {console.log(this.name);}let f = new Fn("lili", 18);console.log(f);// f -- Fn.prototype -- Object.prototypeconsole.log(f.hasOwnProperty("age"));//trueconsole.log(f.hasOwnProperty("toString"));//falseconsole.log(f.hasOwnProperty("show"));//falseconsole.log(Fn.hasOwnProperty("age"));//falseconsole.log(Fn.hasOwnProperty("toString"));//falseconsole.log(Fn.hasOwnProperty("show"));//trueconsole.log(Object.hasOwnProperty("age"));//falseconsole.log(Object.hasOwnProperty("toString"));//trueconsole.log(Object.hasOwnProperty("show"));//false</script>
</body>

【思考题】编写一个hasPubProperty方法,检测一个属性是不是公有的。

<body><script>function Fn(name, age) {this.name = name;this.age = age;}var f1 = new Fn("lili", 18);Object.prototype.hasPubProperty = function (attr) {//如果是true,说明不论是公有还是私有,起码是if (attr in this) {if (!this.hasOwnProperty(attr)) {// 不是私有的说明就是公有的return true;}return false;}return false;}console.log(f1.hasPubProperty("toString"))//trueconsole.log(f1.hasPubProperty("name"))//false</script>
</body>

进阶版:解决某个属性即是公有属性,也是私有属性。

使用静态方法Object.getPrototypeOf()获取到原型对象

<body><script>Object.prototype.hasPubProperty = function (attr) {//查找f1的原型//let proto = this.__proto__;找到实例对象的原型对象,浏览器不支持该写法,//使用Object.getPrototypeOf(this)代替let proto = Object.getPrototypeOf(this);while (proto) {//如果attr是原型的私有,就返回trueif (proto.hasOwnProperty(attr)) { return true }//一直往上找,直到nullproto = Object.getPrototypeOf(proto);}return false;}function Fn(name, age) {this.name = name;this.age = age;}Object.prototype.age = 180;let f1 = new Fn("lili", 18);console.log(f1.hasPubProperty("name")); //falseconsole.log(f1.hasPubProperty("age")); //trueconsole.log(f1.hasPubProperty("toString")); //true</script>
</body>

2.9 构造函数原型重定向

2.9.1 原型重定向的类型

手动重定向的原型是没有constructor的,我们需要自己手动添加一个

语法:constructor:构造函数名

1. 类型一:正常new了一个实例对象。

<body><script>function Fn() {this.x = 100;}Fn.prototype.getX = function () {return this.x;}var f1 = new Fn();Fn.prototype = {//constructor:Fn,getY: function () {return this.x}};var f2 = new Fn();console.log(f1.getX());//f.x==100console.log(f2.getX()); // 报错console.log(f1.constructor);//Fn构造函数console.log(f2.constructor); // Object内置类</script>
</body>

2. 类型二:构造函数的原型(prototype) new了一个

<body><script>function Fn() {let a = 1;this.a = a;}Fn.prototype.say = function () {this.a = 2;}Fn.prototype = new Fn;let f1 = new Fn;Fn.prototype.b = function () {this.a = 3;}console.log(f1.a);console.log(f1.prototype);console.log(f1.b);console.log(f1.hasOwnProperty("b"));console.log("b" in f1);console.log(f1.constructor == Fn);</script>
</body>

2.9.2 原型重定向的练习

练习1:

<body><script>function C1(name) {//私有:name---undefined 没有,找公有//不进入ifif (name) {this.name = name;}}function C2(name) {//私有:name---undefined//this.name = undefinedthis.name = name;}function C3(name) {//私有:name---undefined---false//this.name = "join"this.name = name || "join";}C1.prototype.name = "Tom";//c1公有C2.prototype.name = "Tom";//c2公有C3.prototype.name = "Tom";//c3公有alert(new C1().name + new C2().name + new C3().name);//“Tomundefinedjoin”</script>
</body>

练习2:

<body><script>function Fn(num) {this.x = this.y = num;}Fn.prototype = {x: 20,sum: function () {console.log(this.x + this.y);}};let f = new Fn(10);console.log(f.sum === Fn.prototype.sum);//truef.sum();//f.x+f.y=20Fn.prototype.sum();//Fn.prototype.x+Fn.prototype.y=20+undefined=NaNconsole.log(f.constructor);//Object内置类</script>
</body>

练习3:

<body><script>function fun() {this.a = 0;this.b = function () {alert(this.a);}}fun.prototype = {//constructor:fun,b: function () {this.a = 20;alert(this.a);},c: function () {this.a = 30;alert(this.a)}}var my_fun = new fun();my_fun.b();//0my_fun.c();//30</script>
</body>

练习4:补全下面代码,使最后控制台输出的内容为15(10+10-5)。

<body><script>let n = 10;Number.prototype.plus = function (num = 0) { };Number.prototype.minus = function (num = 0) { };let m = n.plus(10).minus(5);console.log(m); </script>
</body>

2.10 内置类的原型重定向

内置类的原型不允许重定向的即使重定向也没有作用

但是原型上的方法可以重写把原来的方法给覆盖

<body><script>var arr = [1, 2, 3];//重定向前的原型链:arr-- Array.prototype---Object.prototype---nullArray.prototype = {};//重定向//重定向后的原型链还是:arr-- Array.prototype---Object.prototype---nullconsole.log(arr);//方法重写:Array.prototype.push = function push() {return this.length;};console.log(arr.push());console.log(arr);</script>
</body>

3. 函数的三种角色

1.普通函数

2.普通对象

        数组/Object...内置类的键值对当做普通对象,这里放的属性方法(静态私有属性)是工具类方法,和它的实例没有关系---》Array.form()/Array.isArray()/Object.create()...。

3.类(构造函数)

function Fn(x,y){var total=x+y;this.a=x;this.b=y;this.total=total;}Fn(); // 当成普通函数执行var f1=new Fn(1,2); // 构造函数Fn.myName="lili"; // 普通的对象 console.log(Fn);

4.JS中的优先级

优先级运算符类型结合性运算符
19分组n/a(不相关)( … )
18成员访问从左到右.
需计算的成员访问从左到右… [ … ]
new(带参数列表)n/anew … ( … )
函数调用从左到右… ( … )
可选链(Optional chaining)从左到右?.
17new(无参数列表)从右到左new …
16后置递增n/a… ++
后置递减… --
15逻辑非 (!)从右到左! …
按位非 (~)~ …
一元加法 (+)+ …
一元减法 (-)- …
前置递增++ …
前置递减-- …
typeoftypeof …
voidvoid …
deletedelete …
awaitawait …
14幂 (**)从右到左… ** …
13乘法 (*)从左到右… * …
除法 (/)… / …
取余 (%)… % …
12加法 (+)从左到右… + …
减法 (-)… - …
11按位左移 (<<)从左到右… << …
按位右移 (>>)… >> …
无符号右移 (>>>)… >>> …
10小于 (<)从左到右… < …
小于等于 (<=)… <= …
大于 (>)… > …
大于等于 (>=)… >= …
in… in …
instanceof… instanceof …
9相等 (==)从左到右… == …
不相等 (!=)… != …
一致/严格相等 (===)… === …
不一致/严格不相等 (!==)… !== …
8按位与 (&)从左到右… & …
7按位异或 (^)从左到右… ^ …
6按位或 (|)从左到右… | …
5逻辑与 (&&)从左到右… && …
4逻辑或 (||)从左到右… || …
空值合并 (??)从左到右… ?? …
3条件(三元)运算符从右到左… ? … : …
2赋值从右到左… = …
… += …
… -= …
… **= …
… *= …
… /= …
… %= …
… <<= …
… >>= …
… >>>= …
… &= …
… ^= …
… |= …
… &&= …
… ||= …
… ??= …
1逗号 / 序列从左到右… , …

5. 完整的原型图

结论: Function 和 Object 内置类的关系

几乎所有的函数身上都有一个prototype _ _proto_ _。

-------------------------------------------------------------------------------------------------------------------------

结论一:

1. Function.__proto__.__proto__ === Object.prototype  ,Function是一个函数,但是函数也是对象,函数最终也是Object 的一个实例

2. Function.prototype.__proto__ === Object.prototype  ,Function.prototype(函数的原型对象)是Object的一个实例。

3. Object.__proto__.__proto__ === Object.prototype  ,Object是Object的一个实例。

4. 构造函数.__proto__.__proto__ === Object.prototype  构造函数是Object的一个实例。

5. 构造函数.prototype.__proto__ === Object.prototype  ,构造函数的原型对象是Object的一个实例。

6. 实例对象.__proto__.__proto__ === Object.prototype ,实例对象是Object的一个实例。

总结:万物皆对象,都可以使用对象原型(Object.prototype)上的方法和属性。

-------------------------------------------------------------------------------------------------------------------------

结论二:

1. Function.__proto__ === Function.prototype Function是Function的一个实例

2. Object.__proto__ === Function.prototype  ,Object本身就是一个函数 ,Object是Function的一个实例。

3.  构造函数.__proto__ === Function.prototype 构造函数是Function的一个实例。

总结:所有的函数沿着原型链都能找到Function原型上方法和属性(call,bind,apply),为所有函数实例提供公有的属性和方法。

重点注意:Function.prototype 是一个匿名空函数,但是作用跟原型对象一样。

-------------------------------------------------------------------------------------------------------------------------

Object 是所有类的基类。

Object的内置对象堆 , 其实是一个函数。

Object的内置对象堆内存放这仅他自己可以调用的静态方法(工具类方法),与实例没有关系。

Function内置类对象堆(函数),所有函数都是它的一个实例。

前置知识:

new Foo().getName();等价于

var a = new Foo()
a.getName()

因为成员访问(.)的优先级高于new 函数名。

new new Foo().getName();等价于

var a = new Foo();
var b = a.getName;
var c = new b();

因为new 函数名()的优先级高于成员访问(.),成员访问(.)的优先级高于new 函数名。

阿里超难面试题(JS 中运算符优先级)

<body><script>function Foo() {getName = function () {console.log(1);};return this;}Foo.getName = function () {console.log(2);};Foo.prototype.getName = function () {console.log(3);};var getName = function () {console.log(4);};function getName() {console.log(5);}Foo.getName();getName();Foo().getName();getName();new Foo.getName();new Foo().getName();new new Foo().getName();</script>
</body>

6. JS中的设计模式

6.1 工厂模式

        把实现相同功能的代码进行封装,后期在使用的时候,只用调用这个函数即可,方便后期的“批量生产”。减少了页面中冗余的代码,实现了“高耦合低内聚”

工厂模式总结:批量生成,函数封装,实现了“高耦合低内聚”

<body><script>function createObj(name, age) {return {name,age}}let obj1 = createObj("小米", 18);console.log(obj1);//obj{name:"小米",age=18}let obj2 = createObj("小王", 19);//obj{name:"小王",age=19}console.log(obj2);</script>
</body>

6.2 单例模式

        可以把描述一个事物的所有属性放到一个对象中,这样避免了相互的干扰,这种设计模式就是单例设计。

单例模式总结:每个都是一个单独的个体,不会冲突

简单单例模式:对象

高级单例模式:闭包

简单单例模式: 

<body><script>let obj1 = {name: "小王",age: 18}let obj2 = {name: "小米",age: 19}console.log(obj1.name, obj2.name);</script>
</body>

高级单例模式:

<body><script>let lunBox = (function () {var a = 10;function show() { }return {};})();let pubuBox = (function () {var a = 100;function show() { }return {};})();let nameSpace = (function () {function fn() { }var a = 2; //... 在这个作用域中还有很多的变量和方法, return { // 想要把谁暴露出去,就放到这对象中 fn: fn}})();console.log(nameSpace.fn)</script>
</body>

6.3 构造函数模式

6.3.1 什么是构造函数模式?

        自己创建一个函数执行的时候用“new”来执行,这样这个函数就是类(构造函数),返回的结果是这个类的一个实例对象

构造函数的语法:
有参new 函数名();,优先级更高18

无参new 函数名;,优先级低一点17

<body><script>function Fn(x, y) {let sum = 10;this.total = x + y;this.say = function () {console.log(`我计算的和是:${this.total}`);};// return {name:'哈哈'};}let res = Fn(10, 20); //普通函数执行let f1 = new Fn(10, 20); //构造函数执行,传参,优先级更高18let f2 = new Fn;//构造函数执行,没传参,优先级低一点17console.log(f1.sum); //undefinedconsole.log(f1.total); //30 console.log(f1.say === f2.say);//false</script>
</body>

6.3.2 普通函数的执行上下文:

6.3.3 构造函数的执行上下文(也会像普通函数一样创建一个私有上下文

 1.私有上下文产生后,首先创建这个类的实例对象

2. this指向这个创建的实例对象

3. this.xxx=xxx;实例对象设置私有属性和方法

4.如果函数没有写返回值 或者 返回的是原始类型的值默认返回值创建的实例对象;如果函数返回值,是对象类型的值

6.4 观察者模式

学到后期更新

6.5 发布订阅模式

学到后期更新

6.6 承诺者模式

学到后期更新

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词