Bootstrap

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

1. 面向对象

1.1 什么是面向对象

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

         对象:万物皆对象。

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

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

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

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

<body>
    <script>
        let arr = new Array();
        console.dir(Array);
        console.dir(Array.name);//Array
        console.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); //false
        console.log(f1.getY === f2.getY); //true
        console.log(f1.__proto__.getY === Fn.prototype.getY); //true
        console.log(f1.__proto__.getX === f2.getX); //false
        console.log(f1.getX === Fn.prototype.getX); //false
        console.log(f1.constructor); //Fn
        console.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);//true
        console.log(arr instanceof Number);//false
        console.log(arr instanceof Object);//true

        function 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.prototype
        console.log(f instanceof Fn);//true
        console.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);//false
        console.log(name in f);//false
        console.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.prototype
        console.log("toString" in Object.prototype);//true
        console.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.prototype
        console.log(f.hasOwnProperty("age"));//true
        console.log(f.hasOwnProperty("toString"));//false
        console.log(f.hasOwnProperty("show"));//false

        console.log(Fn.hasOwnProperty("age"));//false
        console.log(Fn.hasOwnProperty("toString"));//false
        console.log(Fn.hasOwnProperty("show"));//true

        console.log(Object.hasOwnProperty("age"));//false
        console.log(Object.hasOwnProperty("toString"));//true
        console.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"))//true

        console.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是原型的私有,就返回true
                if (proto.hasOwnProperty(attr)) { return true }
                //一直往上找,直到null
                proto = 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")); //false
        console.log(f1.hasPubProperty("age")); //true
        console.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==100
        console.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 没有,找公有
            //不进入if
            if (name) {
                this.name = name;
            }
        }

        function C2(name) {
            //私有:name---undefined
            //this.name = undefined
            this.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);//true
        f.sum();//f.x+f.y=20
        Fn.prototype.sum();//Fn.prototype.x+Fn.prototype.y=20+undefined=NaN
        console.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();//0
        my_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---null
        Array.prototype = {};//重定向
        //重定向后的原型链还是:arr-- Array.prototype---Object.prototype---null
        console.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); //构造函数执行,传参,优先级更高18
        let f2 = new Fn;//构造函数执行,没传参,优先级低一点17
        console.log(f1.sum); //undefined
        console.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 承诺者模式

学到后期更新

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;