JavaScritp面向对象之单例工厂模式和构造函数的学习

单例模式

var name1 = 'wangwu';
var age1 = 25;

var name2 = 'zhangsan';
var age2 = 26;

以上的写法是描述两个人,每个人都有姓名和年龄,但是每个人的姓名和年龄并没有放在一起,也就是说每个人的年龄和姓名并没有对应起来

对象的概念:

把描述同一个事物(同一个对象)的属性和方法放在同一个内存空间下面,起到了分组的作用,这样不同事物之间的属性即使属性名相同相互也不会发生冲突

var person1 = {
    name:'wangwu',
    age:25
};

var person2 = {
    name:'zhangsan',
    age:26
};

分组之后,每一个人的姓名和年龄都在同一块内存空间下,也就是每个人的姓名和年龄都对应起来了。

我们也把这种分组编写代码的模式称之为单例模式

而 person1,person2 叫做命名空间。

模块化开发

我们可以使用单例模式来进行模块化开发

一般情况下会根据当前项目的需求,划分几个功能模块,每个人负责一部分,同时开发,最后把每个人的代码进行合并。

var myOn = {
    change:function(){
        this.clickEvent(); // 在自己的命名空间下调用自己命名空间的方法
    },
    clickEvent:function(){
        console.log('clickEvent...')
    }
}
var youOn = {
    change:function(){

    }
}

myOn 和 youOn 都有change方法,如果没有分模块来编写的话会造成命名的冲突,按照JavaScript语言的解析规则,后声明的方法会覆盖前面声明的方法。

而通过模块划分之后,这两个模块下的change方法归属于各自的模块,调用的时候都是调用自己模块下的方法不会有冲突。

注意

单例模式解决了分组的问题,让每个对象有了自己独立的命名空间,但是不能批量生产,每一个新的对象都要重新写一份一模一样的代码。

工厂模式

把实现同一事情的相同代码,放到一个函数中,以后如果再想实现这个功能,就不需要重新编写这些代码了,只要执行当前的函数即可。

这就是函数的封装,体现了高内聚、低耦合的思想:减少页面的中的冗余代码,提高代码的重复利用率:

function Tab(){
    //添加原料
    let obj = {}
    //加工原料
    obj.tablength = 1
    obj.psFor = function(){
        console.log("psFor...")
    }
    //出厂
    return obj
}

问题:

对象识别问题:也就是不能识别出来是来自那个工厂生产出来的 最终指向 Object

性能问题:

let str = new String() //==>可以判断类型

如果用工厂模式就不可以知道是什么

公共空间:原型 prototype ===》 new 运算符:

构造函数

new 运算符

  1. 执行函数
  2. 自动创建一个空对象
  3. 把创建的对象指向另一个对象
  4. 把控对象和函数里面的this 衔接起来
  5. 隐式返回this
function test(){
    console.log("text...")
}
new test;
//加不加小括号都可以

可以用来简化工厂模式 就简化成构造函数了

function Tab(){
    //let obj = {} //===> this
    this.name = "张三"
    this.hobby = function(){
        console.log('篮球')
    }
    // return obj
}
let tab1 = new Tab()
//直接调用函数
var res = myfun('xx' , 7);

没有用new而直接调用函数的方式,不是构造函数而是普通函数执行

构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例

执行的时候

  1. 普通函数执行:myfun()
  2. 构造函数执行:new myfun(),通过new执行后,myfun 就是一个类了,而函数的返回值就是myfun 这个类的一个实例。

约定

  1. 首字母大写
  2. 属性放在构造函数里面
  3. 方法放在原型里面

image-20200917135250967

Tab.prototype.psFor= function(){
    console.log('psFor...')
}

每个原型上都有一个预定义的属性: constructor ===》构造函数 会覆盖原本的 constructor属性

下面的写法是不对的

Tab.prototype = {
    psFor(){
        console.log('psFor...')
    }
}

但是可以在里面写上 constructor

Tab.prototype = {
    constructor:Tab,
    psFor(){
        console.log('psFor...')
    }
} 

constructor 用处

判断类型(如果被覆盖了 在用下面的方案 就会出问题)

let str = "javascript"
if(str.constructor === String){
        console.log("是字符串")
}else{
        console.log("不是字符串")
}

仿写 new 运算符

function myNew(constructor,...arg){
        //1.创建对象
        let obj = {}
        //2.改变this指向
        constructor.call(obj,...arg)
        //3.原型覆盖
        obj.__proto__ = constructor.prototype
        return obj
}

私有变量

function Fn() {
    var num = 10;
    this.x = 100; // f1.x = 100
    this.getX = function () { // f1.getX=function
        console.log(this.x);
    };
}
var f1 = new Fn;
console.log(f1.num); // -> undefined
console.log(f1.x); //-> 100

类有普通函数的一面,当函数执行的时候,var num其实只是当前形成的私有作用域中的私有变量而已,它和f1这个实例没有任何的关系,只有this.xxx=xxx才相当于给f1这个实例增加私有的属性和方法,才和f1有关系。

继承

function Person(name){
    this.name = name;
    this.eyes = "两只";
    this.legs = "两条";
}
function Student(name){
    Person.call(this,name)
    this.className = "二班";
}
let newPerson = new Student("张三");
console.log(newPerson.className);

简单原型继承,出现影响父类的情况;

function Person(name){
    this.name = name;
    this.eyes = "两只";
    this.legs = "两条";
}
function Student(name){
        //继承
    Person.call(this,name)
    this.className = "二班";
}
Student.prototype = Person.prototype  //直接赋值

组合继承

function Dad(){
    this.name = "张三";
}
Dad.prototype.hobby = function(){
    console.log("喜欢篮球");
}
function Son(){
    Dad.call(this);
}
let F = function(){}
F.prototype = Dad.prototype;
Son.prototype = new F();
Son.prototype.constructor = Son;

let newSon = new Son();
newSon.hobby();

传值和传地址

传值和传址问题

let obj = {
        name:"张三",
        age:20
}
let obj2 = obj
obj2.age = 30
console.log(obj) //=> {name:"张三",age:30}

如果拷贝对象包含函数,或者undefined等值,此方法就会出现问题

let obj = {
        name:"张三",
        age:20,
        test:undefined,
        fn:function(){
            console.log('fn...s')
        }
}
//序列化然后在转化回来
//如果拷贝对象包含函数,或者undefined等值,此方法就会出现问题
let obj2 = JSON.parse(JSON.stringify(obj))
obj2.age = 30
console.log(obj,obj2) 
//=> obj:{name: "张三", age: 20, test: undefined, fn: ƒ} 
//=> obj2:{name: "张三", age: 30}

深拷贝

//递归深拷贝
function deepCopy(obj){
    let newObj = Array.isArray(obj)?[]:{};
    for(let key in obj){
            if(obj.hasOwnProperty(key)){
            if(obj.hasOwnProperty(key)){
              if(typeof obj[key] == "object"){
                if(obj[key] === null){
                  newObj[key] = null
                }else{
                  newObj[key] = deepCopy(obj[key]);
                }
              }else{
                  newObj[key] = obj[key];
              }
            }
            }
    }
    return newObj;
}

原型链

es6类和继承

//类型为 function
class Drag{
        //静态属性
        static height = "188cm"
        //静态方法
        static text(){
            console.log('text...')
        }
        constructor(){
                this.name = "张三"
        }
        hobby(){
                console.log("篮球")
        }
}
console.log(Drag.height)
let drag = new Drag()
console.log(drag)

继承

extends

extends 继承关键字

关键字用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。

继承的.prototype必须是一个Object或者 null

class ChildClass extends ParentClass { ... }
class LimitDrag extends Drag{
    constructor(){
        super()
    }
}

使用 extends与内置对象

这个示例继承了内置的Date对象。

class myDate extends Date {
  constructor() {
    super();
  }

  getFormattedDate() {
    var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
    return this.getDate() + "-" + months[this.getMonth()] + "-" + this.getFullYear();
  }
}

扩展 null

可以像扩展普通类一样扩展null,但是新对象的原型将不会继承 Object.prototype

class nullExtends extends null {
  constructor() {}
}

Object.getPrototypeOf(nullExtends); // Function.prototype
Object.getPrototypeOf(nullExtends.prototype) // null

new nullExtends(); //ReferenceError: this is not defined

案例

用鼠标进行拖拽

<div class="mydiv1" style="width: 100px;height: 100px;background: red;position: absolute;"></div>

面向过程实现

 let mydiv1 = document.querySelector('.mydiv1')
 mydiv1.onmousedown = (e)=>{
     let ev = e || window.event
     let x = ev.clientX - mydiv1.offsetLeft
     let y = ev.clientY - mydiv1.offsetTop
     mydiv1.onmousemove = (e) =>{
         let ev = e || window.event
         let xx = ev.clientX
         let yy = ev.clientY
         mydiv1.style.left = xx - x + 'px'
         mydiv1.style.top = yy - y + 'px'
     }
     mydiv1.onmouseup = () =>{
             mydiv1.onmousemove = ""
     }

}

面向对象实现

                 function Drag(ele){
            this.ele = ele
            this.downFn()
        }

        Drag.prototype.downFn = function(){
            this.ele.onmousedown = (e)=>{
                let ev = e || window.event
                let x = ev.clientX - this.ele.offsetLeft
                let y = ev.clientY - this.ele.offsetTop
                this.moveFn(x,y)
                this.upFn()
            }
        }
        Drag.prototype.moveFn = function(x,y){
            this.ele.onmousemove = (e) =>{
                let ev = e || window.event
                let xx = ev.clientX
                let yy = ev.clientY
                this.ele.style.left = xx - x + 'px'
                this.ele.style.top = yy - y + 'px'
            }
        }
        Drag.prototype.upFn = function(){
            this.ele.onmouseup = () =>{
                this.ele.onmousemove = ""
            }
        }

调用

let mydiv1 = document.querySelector('.mydiv1')
let drag1 = new Drag(mydiv1)

Sep-17-2020 15-04-40

通过继承给个别的添加 功能

            function LimitDrag(ele){
            Drag.call(this,ele)

        }
        let Link = function(){}
        Link.prototype = Drag.prototype
        LimitDrag.prototype = new Link()
        LimitDrag.prototype.constructor = LimitDrag
        LimitDrag.prototype.setStyle = function(left,top){
            left = left<0?0:left
            top = top<0?0:top
            this.ele.style.left = left + 'px'
            this.ele.style.top = top + 'px'
        }

        let mydiv1 = document.querySelector('.mydiv1')
        let drag1 = new LimitDrag(mydiv1)
上一篇

JavaScritp面向对象之包装对象和对象类型判断