第二节单利、工厂、构造函数、原型链、call、bind、apply、sort

--------------------------------------------------单例模式----------------------------------------------------
var person1={
    name:'张三',
    age:18
};
var person2={
    name:'李四',
    age:19
}
对象数据类型的作用:
把描述同一个事物或的属性和方法放在一个内容空间下,起到了分组的作用,这样不同事物之间的属性即使属性名相同,
相互也不会冲突,我们把这种编写代码的模式叫做"单例模式"
在单例模式中我们把person1和person2这两个名字叫做"命名空间"

单例模式是一种项目开发中经常使用的模式,因为项目中我们可以使用单例模式进行我们的“模块化开发”
“模块化开发”对于一个相对来说比较大的项目需要多人协作开发,我们一般情况下会根据项目的需求划分几个功能板块,每个人负责一部分,同时开发,最后把每个人的代码合并
上课举例:
  //公共模块
    var utils = {
        select: function () {

        }
    };
    var serarhRender = {
        change: function () {
             utils.select();//在自己的命名空间下调用其他命名空间的方法
        }
    };
    var typeRender = {
        change: function () {
           this.clickevent();//在自己的命名空间下调用自己命名空间下的方法
        },
        clickevent: function () {

        }
    };
    typeRender.change();//.前面是谁,这个方法中的this就是谁
-----------------------------------------------工厂模式---------------------------------------------------------
单例模式虽然解决了分组作用,但是不能实现批量生产,属于手工作业模式
工厂模式---->把实现同一件事情的相同代码放到一个函数中,以后如果想实现这个功能,不需要重新编写这些代码,
只需要执行当前的函数即可(也叫做函数的封装)(低耦合高内聚)
继承:子类继承父类中的属性和方法
多态:当前方法的多种形态,在后台语言中多态只包含了重载和重写
(js中不存在重载,方法名一样的话,后面的会把前面的覆盖掉,最后只保留一个)
(js中有一个类似重载但不是重载:我们可以根据传递的参数不一样,实现不同的功能)
重写:子类重写父类的方法
js中的多态 (类重载)
 function sum(n){
      if(typeof  n=="undefined"){
          return 0;
      }
      return n;
   }
   sum(100);
   sum();

--------------------------------------构造函数模式基础-------------------------------------------
构造函数基础
构造函数的目的就是为了创建一个自定义类,并且创建这个类的实例
上课举例构造函数:
function Createperson(name, age) {
    this.name = name;
    this.age = age;
    this.writejs = function () {
        console.log(this.name + 'aaaa');
    };
}
var p1 =new Createperson('hong', 18);
p1.writejs();
var p2 =new Createperson(ming, 18);
p2.writejs();

构造函数和工厂模式的区别?
1、执行的时候
普通函数执行----->createperson();
构造函数模式执行----->new createperson() 通过new执行后我们的createperson就是一个类了,js中所有的类都是函数数据类型的,但是它本身就是普通函数
p1就是createperson类的实例,js中所有的实例都是对象数据类型的

2、在函数代码执行的时候
相同:都是形成一个私有的作用域,然后经历了形参赋值,预解释,代码从上往下执行(类和普通函数一样,它有普通函数的一面)
不同:在代码执行之前不用自己手动创建对象了,浏览器会默认的创建一个对象数据类型的值(这个对象数据类型的值就是我们当前类的一个实例),接下来代码从上到下执,以当前的实例为执行主体(this代表的就是当前实例),然后分别把属性名和属性值赋给当前的实例
注意:
1、js中所有的类都是函数数据类型的,它通过new执行变成了一个类,它本身还是一个普通的函数,js中所有的实例都是对象数据类型的
2、在构造函数中this.xxx=xxx出现的this指的是当前类的实例
3、p1和p2都是createperson这个类的实例,都拥有wirteJs这个方法,但是不同实例之间的方法是不一样的
在类中给实例增加属性(this.xxx=xxx)属于当前实例的私有的属性,实例和实例之间是单独的个体,所以私有的属性之间是不相等的
上课作业:
    function Fn() {
        this.x = 100;
        this.getx = function () {
            console.log(this.x);
        }
    }
    var f1 = new Fn;
    f1.getx();
    var ss = f1.getx;
    ss();

--------------------------------------------构造函数模式扩展----------------------------------------------------
上课案例1:
    function Fn() {
        var num = 10;
        this.x = 100;
        this.getX = function () {
            console.log(this.x);
        };
    }
    var f1 = new Fn;
    console.log(f1.num);

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

上课思考2:
    function Fn() {
        this.x = 100;
        this.getX = function () {
            console.log(this.x);
        };
        return 100;
    }
    var f1 = new Fn;
console.log(f1);

在构造函数模式中,浏览器会默认的把我们的实例返回(返回的是对象数据类型的值),如果我们自己动手写了return返回,会有两种情况考虑。
1、返回的是一个基本数据类型的值,返回的是当前的实例,与基本数据类型无关
2、返回的是引用数据类型的值,当前的实例会被自己返回的值给替换掉,这样f1就不再是Fn的实例了


上课思考案例3:检测数据类型初探
对于检测数据类型来说,typeof有自己的局限性,不能细分object下的对象
    function Fn() {
        this.x = 100;
        this.getX = function () {
            console.log(this.x);
        };
    }
var f1=new Fn;

1、检测某一个实例是否属于这个类-------》 instanceof 
console.log(f1 instanceof Fn);   true

console.log(f1 instanceof Object);   true
因为所有的实例都是对象数据类型的,而每一个对象数据类型都是Object这个内置类的一个实例,所以f1也是Object的一个实例

 var a=[];
 console.log(a instanceof Array);  true  说明a是一个数组

2、检测某一个属性是否属于这个对象 ---------》in
(无论是私有的还是公有的属性,只要存在,用in来检测都是true)
console.log("x" in f1);

3、检测某一个属性是否为这个对象的“私有属性”-----------》hasOwnProperty
 console.log(f1.hasOwnProperty("getX"));

4、检测某一个属性是否为该对象的“公有属性”
    function publicfn(attr,obj){
        return (attr in obj) && !obj.hasOwnProperty(attr);
    }

----------------------------------------原型链模式基础----------------------------------------------------
构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立开的,我们称为实例识别
基于构造函数模式的原型模式解决了属性或者方法公有的问题,把实例之间相同的属性和方法提取成公有的属性和方法,想让谁公有就把它放在类的原型上(createperson.prototype)
如下所示案例1:
    function Fn() {
        this.x = 100;
    }
    Fn.prototype.getX = function () {
        console.log(this.x);
    };
    var f1 = new Fn;
var f2 = new Fn;

Js中规定的规则:
1、每一个函数数据类型(普通函数、类)都有一个天生自带的属性:prototype(原型)
属性值是对象数据类型的值
2、在prototype上,浏览器天生给它加了一个属性,constructor(构造函数),属性值是当前函数(类)本身
3、每一个对象数据类型(普通的对象、实例、prototype)也天生自带一个属性,叫做__proto__,属性值是当前实例所属类的原型
4、所有的对象数据类型的都是Object的一个实例

注意:
Object是js中所有对象数据类型的基类(最顶层的类)
1、f1 instanceof Object---->true因为f1通过__proto__可以向上级查找,不管多少级,最后总能找到Object
2、在Object.prototype上没有__proto__这个属性

原型链查找机制:
1>通过对象名.属性名的方式获取属性值的时候,首先在对象的私有的属性上进行查找,如果私有属性中存在这个属性,则获取的是私有的属性值
2>如果私有的没有,则通过__proto__找到所属类的原型(类的原型上定义的属性和方法都是当前实例公有的属性和方法),原型上存在的话,获取的是公有的属性值
3>如果原型上也没有,则继续通过原型上的__proto__继续向上查找,一直找到Object.prototype为止
继续思考案例1:
console.log(f1.getX==f2.getX);
console.log(f1.__proto__.getX==f2.getX);
console.log(f1.getX==Fn.prototype.getX);

在ie浏览器中原型也是同样的原理,但是ie浏览器怕你通过__proto__把公有的修改,禁止我们使用__proto__。
-------------------------------原型链模式扩展(this和原型扩展)------------------------------
思考1:
function Fn(){
        this.x=100;
        this.y=200;
        this.gety= function () {
            console.log(this.y);
        };
    }
    Fn.prototype={
        constructor:Fn,
        y:300,
        getx: function () {
            console.log(this.x);
        },
        gety: function () {
          console.log(this.y);
        }
    };
    var f=new Fn;
    f.getx();
    f.__proto__.getx();
    Fn.prototype.getx();
    f.gety();
f.__proto__.gety();


思考2:this指向问题
    Array.prototype.myfn = function () {console.log(this)};
    var arr = [];//实例化一个对象
    arr.myfn();//myfn中的this指的是arr
Array.prototype.myfn(); //myfn中的this指的是Array.prototype

思考3:在原型上实现数组去重
    Array.prototype.myfn = function () {
        var obj = {};
        for (var i = 0; i < this.length; i++) {
            var cur = this[i];
            if (obj[cur] == cur) {
                this[i] = this[this.length - 1];
                this.length--;
                i--;
                continue;
            }
            obj[cur] = cur;
        }
    };
    var arr = [12, 12, 13];//实例化一个对象
    arr.myfn();
console.log(arr);

思考4:链式写法
    var arr = [12, 34, 56, 6, 13, 45, 67];
    arr.sort(function (a,b) {
        return a-b;
    }).reverse().pop();

链式写法----->执行完成数组的一个方法可以紧接着执行下一个方法
原理:arr为什么可以使用sort方法,因为sort是Array.prototype上公有的方法,而数组arr是Array这个类的一个实例,所以arr可以使用sort方法,--->只有数组才能使用我们Array原型上定义的属性和方法

作业:链式写法实现数组去重并且从大到小排序

---------------------------原型链模式扩展(批量设置公有属性)---------------------------------------------
    在原型上增加公有的属性和方法:
    1、起别名的方式
    2、重构原型对象的方式:自己开辟一个堆内存,存储我们的公有的属性和方法,把浏览器原来给Fn.prototype开辟的那个替换掉
       1、别名方式
        function Fn() {}
        Fn.prototype.getx = function () {};
        Fn.prototype.gety = function () {};
        Fn.prototype.getz = function () {};
        var f1 = new Fn;

       2、重构原型对象的方式
    function Fn() {}
    Fn.prototype = {
        constructor:Fn,
        a: function () {},
        b: function () {}
    };
    var f = new Fn;
    console.log(f.constructor);

 注意:
 1、只有浏览器天生给Fn.prototype开辟的堆内存才有constructor,而我们自己开辟的这个堆内存没有这个属性,这样constructor指向就变成了Object了,为了和原来保持一致我们需要手动增加constructor的指向
2、用这个方式给内置类增加公有的属性
    Array.prototype = {
        constructor: Array,
        myfn: function () {}
    };
    console.dir(Array.prototype);
我们这种方式会把之前存在于原型上的属性和方法给替换掉,所以我们这种方式修改内置类的话,浏览器会屏蔽掉
但是我们可以一个个修改内置的方法,当我们通过下面的方式在数组的原型上增加方法,如果方法名和原来内置的重复了,会把人家的内置修改掉,所以我们以后在内置类的原型上添加方法,命名都需要添加特殊的前缀
    Array.prototype.sort= function () {
       console.log('aaa');
    };
    var arr=[12,13,14];
    arr.sort()
    
--------------------------深入扩展原型链模式常用的五种继承方式------------------------
知识小扩展1(如何只遍历私有的属性?)
1、for in循环在遍历的时候可以把自己私有的和在它所属类的原型上扩展的属性和方法都可以遍历到,但是一般情况下,我们遍历对象只需要遍历私有的即可,我们可以使用以下的判断进行处理
2、在原型上的方法都是不可枚举的
    Object.prototype.aaa = function () {};
    var obj = {name: '张三', age: 18};
    for (var key in obj) {
        //只把私有的遍历到
        if(obj.propertyIsEnumerable(key)){
            console.log(key);
        }
        //或者(和以上原理相同)
        if(obj.hasOwnProperty(key)){
            console.log(key);
        }
    }
console.dir(Object.prototype);

知识小扩展2:Object.create()有兼容性问题
Object.create()  创建一个新的空对象,这个对象的原型是第一个参数值
var obj = {name: '张三', age: 18};
var obj3 = Object.create(obj);
console.dir(obj3);
思考1:
    var obj = {
        getx: function () {

        }
    };
    var obj2 = Object.create(obj);
    obj.gety = function () {
        console.log('ok');
    };
    obj2.gety();

思考自己封装一个类似于Object.create()
    var obj = {
        getx: function () {}
    };
    function myfn(n) {
        function Fn() {}
        Fn.prototype = n;
        return new Fn;
    }
    var obj11 = myfn(obj);
    console.log(obj11);


第一种:原型继承
    function A(){
        this.x=100;
    }
    A.prototype.getx= function () {
        console.log(this.x);
    };
    function B(){
        this.y=200;
    }
B.prototype=new A;
var b1=new B;
var b2=new B;
var a1=new A;

原型继承原理:子类B想要继承A中的所有的属性和方法(私有+公有),只需要让B.prototype=new A;

原型继承的特点:父类中私有的和公有的都继承到了子类的原型上变成了子类公有的。

原型链继承核心:原型继承并不是把父类中的属性和方法复制一份一模一样的给B,而是让B和A之间增加了原型链的链接,以后B的实例想要A中的getx()方法,需要一级一级向上查找

重写:子类重写父类的方法(在类的继承当中,由于原型继承并不是把父类拿过来一份一模一样的而是让子类和父类之间增加了一个原型链的一个桥梁,这样就导致了子类或者子类的实例通过原型链机制把父类原型上方法进行修改,导致了父类中其他实例也会受到影响)

第二种:call继承
把父类私有的属性和方法克隆一模一样的给子类私有的属性
    function A() {
        this.x = 100;
    }
    A.prototype.getx = function () {
        console.log(this.x)
    };
    function B() {
        A.call(this);
    }
    var n = new B;
console.log(n.x);

第三种:冒充对象继承
//    冒充对象继承 把父类私有的+公有的克隆一份一模一样的给子类私有的
    function A() {
        this.x = 100;
    }
    A.prototype.getx = function () {
        console.log(this.x)
    };
    function B() {
        var temp = new A;
        for (var key in temp) {
           this[key]=temp[key];
        }
        temp=null;
    }
    var n = new B;
    n.getx();

第四种:混合模式继承  指的是原型继承+call继承   //A执行了两次,私有属性分别在B的私有和公有上。
    function A(){
       this.x=100;
    }
    A.prototype.getx= function () {
        console.log(this.x);
    };
    function B(){
        A.call(this);//n.x=100;
    }
    B.prototype=new A; //B.prototype x  getx
    B.prototype.constructor=B;
    var n=new B;
    n.getx();


第五种:寄生组合式继承  //私有的继承私有的,公有的继承公有的
    function A(){
       this.x=100;
    }
    A.prototype.getx= function () {
        console.log(this.x);
    };
    function B(){
        A.call(this);//n.x=100;
    }
    B.prototype= myFn(A.prototype); //B.prototype x  getx
    B.prototype.constructor=B;
    var n=new B;
    n.getx();

    function myFn(o){
        function Fn(){

        }
        Fn.prototype=o;
        return new Fn;
    }

在ie中不支持__proto__

思考:
    function Fn(num) {
        this.x = this.y = num;
    }
    Fn.prototype = {
        x: 20,
        sum: function () {
            console.log(this.x + this.y);
        }
    };
    var f = new Fn(10);
    console.log(f.sum == Fn.prototype.sum);
    f.sum();
    Fn.prototype.sum();
console.log(f.constructor);

--------------------------------------------------原型深入---------------------------------------------------------
原型链终结版
  在js中最大的类并不是Object类 
  所有的对象都是Object类的一个实例,所有的函数(function)都是Function类的实例
一个函数天生自带的属性既自带prototype,又自带__proto__

---------------------------------------------函数的三种角色------------------------------------------------

一个函数存在多面性
1、本身是普通函数,执行的时候形成私有的作用域(闭包),形参赋值,预解释,代码执行,执行完成后栈内存销毁/不销毁
2、“类”有自己的实例,也有一个叫做prototype属性是自己的原型,他的实例都可以指向自己的原型
3、还是一个普通的对象,和var obj={}中obj一样,就是一个普通对象,它作为对象可以有一些自己私有的属性,也可以通过__proto__找到Function.prototype
这三者之间没有必然联系。

特殊特殊记忆的一点:Function.prototype是函数数据类型的,但是相关操作和对象数据类型操作一模一样

思考:
    function Fn() {
        var num = 500;
        this.x = 100;
    }
    Fn.prototype.getx = function () {
       console.log(this.x);
    };
    Fn.aaa=1000;
    var f=new Fn;
    console.log(f.num);
    console.log(f.aaa);
    console.log(Fn());
    console.log(Fn.aaa);

--------------------------------------call方法深入-----------------------------------------------------
思考:
    var obj= {name: '张三'};
    function fn() {
        console.log(this);
    }
obj.fn();
   这个案例并不能改变this的指向,那如何改变this的指向呢? fn.call(obj)

上课案例思考:
    function fn() {
        console.log(this);
    }
    var obj = {name:"张三"};
    fn.call(obj);
  首先fn通过原型链机制找到Function.prototype的call这个方法,并且让call这个方法执行(call()这个方法的作用是让fn执行,并且把fn中的this变成obj)


思考:
    function fn1() {
        console.log(1);
    }
    function fn2() {
        console.log(2);
    }
    fn1.call(fn2);
    fn1.call.call.call(fn2);
fn1.call.call.call.call.call(fn2);

call方法的作用:让某一个方法中的this变成一个值,然后再让这个方法执行


call的细节知识点:
    function fn1(num1, num2) {
        console.log(num1 + num2);
        console.log(this);
    }
fn1.call(100, 200);
在执行call的时候第一个参数是给fn中的this,第二个及以后的参数就是fn的形参值


------------------------------------------------call、apply、bind的区别---------------------------------------
    var obj={name:'aa'};
    function fn(num1, num2) {
        console.log(this);
        console.log(num1+num2);
    }
    fn.call(obj,100,200);
    fn.apply(obj,[100,200]);
    var aaa=fn.bind(obj,100,200);
aaa();

apply是把要给fn传递的参数值统一放在一个数组中进行操作,和call功能相同,只是语法不同而已。

bind方法在ie6~8不兼容,和call/apply类似,都是用来改变this关键字的,call和apply都是改变this和执行fn函数一起执行,而bind是改变了fn中的this,但是此时并没有把fn这个函数执行,需要手动执行这个函数

-----------------------------------------获取数组中的最大值/最小值--------------------------------
利用了apply传参的时候是一个数组来获取数组中的最大值/最小值
    var arr = [10, -1, 200, 300, 34, 9, 0];
    var min=Math.min.apply(null,arr);
    var max=Math.max.apply(null,arr);
---------------------------------------------------------获取平均数--------------------------------------------
    function svgFn() {
        var arr=Array.prototype.slice.call(arguments);//将类数组转换成数组
//      var arr=[].slice.call(arguments);
        arr.sort(function (a, b) {
            return a - b;
        });
        arr.shift();
        arr.pop();
        return eval(arr.join('+')) / arr.length.toFixed(2);
    }
    var a = svgFn(1, 1, 1, 1, 1, 1, 3, 7);
    console.log(a);

1、将类数组转换为数组:把arguments克隆一份一模一样的数组出来
2、借用数组原型上的slice方法,当slice执行的时候,让方法中的 this变为我要处理的arguments,实现将数组arguments转换为数组






----------------------------------------------类数组转换为数组--------------------------------------------

    var odiv1=document.getElementsByTagName('div')
    odiv1是HTMLCollection元素集合类的一个实例,是一个类数组集合
    var odiv2=document.getElementsByName('a')
NodeList节点集合,是一个类数组集合
思考:如何将类数组集合变成数组而且没有兼容性问题?

//如果try中的代码执行出错了,会默认的去执行catch中的代码
    try{
       console.log(num);
    }catch(e){  //形参一定要写,一般我们起名叫e
   console.log(e.message);   //可以收集当前代码报错的原因
         throw new Error('正在加载哦。。');//手动抛出一条错误信息,终止代码执行
    }finally{
// 一般都不用,无论try中的代码是否报错,都要执行finally中的代码
    }
//将类数组转换成数组
    var utils={
        listToArray: function (likeArray) {
            var arr=[];
            try{
                arr=Array.prototype.slice.call(likeArray);
            }catch(e){
                for(var i=0;i< likeArray.length;i++){
                    arr[arr.length]=likeArray[i]
                }
            }
            return arr;
        }
    };

--------------------------------------------------------sort深入研究----------------------------------------------
回调函数:把一个方法当做参数值传递给另外一个函数B,然后在B执行的过程中,我们随时根据需求让A执行
    ary.sort(function (a, b) {
        //a是每一个执行匿名函数时候,找到数组中的当前项
        //b当前项的后一项
        return a - b;//升序  如果a>b,返回>0,a和b交换位置
        return b - a;//降序  如果b>a,返回>0,a和b交换位置
        //return只要是>0就交换位置,小于或等于0位置不动
    });

    ary.sort(function (a,b) {
        return 1;
        //不管a和b谁大,每一次都返回一个恒大于0的数,也就是每一次a和b都要交换位置,最后的结果就相等于reverse
})
a.name.localeCompare(b.name)//用于非数字之间的比较

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据之美

python基础(5):深入理解 python 中的赋值、引用、拷贝、作用域

在 python 中赋值语句总是建立对象的引用值,而不是复制对象。因此,python 变量更像是指针,而不是数据存储区域, ? 这点和大多数 OO 语言类似吧,...

30070
来自专栏人工智能

数据分析进阶课程笔记(一)

关键词: python的一些函数 python的字典 1、split函数 起到拆分字符的作用。输入为一个string,输出一个list。list由string租...

19090
来自专栏塔奇克马敲代码

第 18 章 用于大型程序的工具

13220
来自专栏Java帮帮-微信公众号-技术文章全总结

JAVA面试题解惑——final、finally和finalize的区别

final、finally和finalize的区别是什么? 这是一道再经典不过的面试题了,我们在各个公司的面试题中几乎都能看到它的身影。final、final...

35860
来自专栏塔奇克马敲代码

第 18 章 用于大型程序的工具

19750
来自专栏开发与安全

虚析构函数? vptr? 指针偏移?多态数组? delete 基类指针 内存泄漏?崩溃?

五条基本规则: 1、如果基类已经插入了vptr, 则派生类将继承和重用该vptr。vptr(一般在对象内存模型的顶部)必须随着对象类型的变化而不断地改变它的指向...

25100
来自专栏Golang语言社区

转--从面向对象的角度看Go语言与Java语言的区别

Go语言风格 GO语言是支持并发编程和内存垃圾回收的编译型静态类型语言,运行效率高,具有较强的可伸缩性(scalable)。它是为软件工程服务而进行的语言设计,...

35560
来自专栏我的技术专栏

漫谈C++:良好的编程习惯与编程要点

13370
来自专栏desperate633

浅谈javascript中的的闭包作用域链引出闭包利用闭包突破作用域链的三种方法小结

闭包可以说是javascript中最令人迷惑的概念了。需要我们在实践中去慢慢理解,在实际编码中,由于闭包的效率和会产生大量无法销毁的内存,所以原则是尽量少使用闭...

7810
来自专栏十月梦想

JavaScript数组对象

万能操作 数组.splice(开始位置,数量,操作),操作后原数组的内容改变第一个参数是指定从几号位置开始删除或添加  第二个参数是指定删除几个元素

10230

扫码关注云+社区

领取腾讯云代金券