js 函数

作者: jasmine_6aa1 | 来源:发表于2020-03-14 13:31 被阅读0次

    前引

    我们在了解函数之前,应该先了解一下堆栈的定义
    基本类型的值也就存放在栈内存中:基本类型占有的内存大小是固定的,比如Boolean String Number undefind null,都是存在栈内存中
    引用类型的值也就存放在堆内存中:引用类型的占有的内存大小不固定,比如Object Array 这些都是在堆内存中的
    我们访问引用类型的时候,我们先是通过变量名去栈内存中拿到该值在堆内存中的地址,然后通过这个地址再去堆内存访问其值。
    栈:存放基本类型(String,Number,Boolean,Null,Undefined),简单的数据段,占据固定大小的空间。
    堆:大小不定也不会自动释放,存放引用类型(Function,Array,Object), 指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。

    注:

    我们创建的所有对象和数组 都是在堆内存中,
    然后你调用数组或对象,其实是调用的数组或对象的地址,
    这个地址就是新开辟的东西,放在栈里面的
    当结束时候,这个栈内存,也就消失了。
    

    1,创建函数的三种方式

    1,函数声明  function name(){}
    2,函数表达式 var name=function(){}
    3,创建实例函数 var name=new function();
    4,自执行函数
        (function(){alert(1);})();
        (function fn1(){alert(1);})();
    

    ES6 中,箭头函数意义:箭头功能没有自己的this,不适合定义对象方法
    函数的经历的历程由:单例模式()=>工厂模式()=>构造函数

    2,单例模式

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

    //  单例模式下,我们把person1与person2叫做  命名空间
    var jsPerson1={
         name:"王小波",
         age:48,
         writeJs:function(){
             console.log("my name is"+this.name+"i can write js~~");
         }
      };
    jsPerson1.writeJs();
    

    单例模式虽然解决了分组的作用,但是还不能实现批量的生产,属于手工作业模式 => 工厂模式产生了

    3,工厂模式

    1,概念:把实现同一件事情的相同代码放到一个函数中,以后如果再想实现这个功能,不需要从新地编写这些代码,只需要执行当前的函数即可叫做函数的封装
    2,代码封装优势:低耦合高内聚:减少页面中的冗余代码,提高代码的重复利用率
    3,所有的编程语言都是面向对象开发的 => 类的继承、封装、多态
    4,继承:子类继承父类中的属性和方法

    function createJsPerson(name,age){
         var obj={};// 工厂模式要在里面定义一个变量
         obj.name=name;
         obj.age=age;
         obj.writeJs = function(){
           console.log("my name is"+this.name+"i can write js~~");
         };
         return obj;
    };
    var p1 = createJsPerson("王小波",12);
    

    工厂模式虽然解决了创建多个相似对象问题,但是没有解决对象识别问题,即不能知道一个对象的类型,所以构造函数产生了

    4,构造函数

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

    2,构造函数的执行过程:

    使用new关键字创建对象
    在内存中创建一个空对象,把this指向刚创建的空对象
    执行构造函数中的代码
    默认返回新创建的对象;不管有没有return,或者return 后面是字符串后者数字等,都不起作用;但是如果 return 都面返回的是一个object,那么就会返回这个object值

    3,构造函数的案例:

    function CreateJsPerson(name,age) {
          this.name = name;
          this.age = age;
          this.writeJs = function(){
             console.log("my name is"+this.name+"i can write js~~");
          };
    }
    
    let p2 = new CreateJsPerson('kevin2',8);
    

    注:在构造函数模式中,new fn;执行时,如果fn不需要传递参数的话,后面的小括号可以省略
    js中所有的类都是函数数据类型的,它通过 new 执行变成了类,但是他本身也是一个普通的函数
    js中所有的实例都是对象数据类型的

    4,判断数据的类型方法

    • instanceof:判断某个对象是否是某个构造函数的实例
    • typeof:检测数据类型作用,不能细分object下面的对象,数组,正则...
    • attr in object:私有公有,只要有都显示true(检测某一个属性是否是属于这个对象)
    • hasOwnProperty:用来检查属性是否为对象的私有属性
    • hasPubProperty:用来检查属性是否为对象的公有属性

    5,构造函数模式与工厂模式区别

    • 执行时
    普通函数执行:var p1=createJsPerson("王小波",12);// createJsPerson是一个函数名
    
    构造函数模式:var p2 = new CreateJsPerson("王小波",12);// CreateJsPerson是一个类
    

    注:而函数执行返回值(p2)就是CreateJsPerson这个类的一个实例
    如果类是我们自己创建的,那么首字母要大写,这个是规范

    • 在函数代码执行的时候
    相同:都是形成一个私有的作用域,然后 形参赋值—>预解释—>代码从上到下执行,类和普通函数一样,他也有普通的一面
    
    不同:
    `工厂模式`:手动创建一个对象
    `构造函数`:在代码执行之前,构造函数不用手动的创建对象,浏览器会默认创建一个对象数据类型的值(这个对象其实就是我们当前类的一个实例)
    接下来代码从上到下执行,以当前的实例为执行的主体(this代表的就是当前的实例),然后分别把属性名和属性值赋值给当前的实例,浏览器会默认地把创建的实例返回
    

    用 CreateJSPerson 这个类创建出来的实例,都拥有 writeJs 这个方法,但是不同实例之间的方法是不一样的

    注:

    1,浏览器在构造函数模式中,浏览器会默认的把我们的实例返回(返回的是一个对象数据类型的值);
    2,如果我们自己手动写return返回:返回的是一个基本数据类型的值,当前返回实例是不变的,返回是的一个引用数据类型的值,当前的实例会被自己的值给替换掉

    5,构造函数原型

    console.log(p1.writeJs === p2.writeJs) // false
    

    问题:当我们创建构造函数时,里面的方法通过创建会存储多份

    • 每个构造函数都有一个属性 => 原型 (prototype)
    • 通过原型添加方法,它的成员都可以访问到,此时存储的方法只有一个
    console.log(p1.writeJs === p2.writeJs) // true
    

    6,对象原型

    5,原型链模式

    1,实例识别:构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立开的

    2,原型链模式:

    1,通过 对象名.属性名(键值对) 的方式获取属性值的时候,
    2,首先在对象的私有属性上进行查找,
    3,如果私有中存在这个属性,则获取的是私有的属性值;
    4,当调用对象属性/方法时,先找对象私有属性/方法,如果没有,就调用原型中的属性/方法
    5,如果元素上没有找到,则继续通过原型上__proto__继续向上查找,一直找到 Object.prototype 为止,没找到会报错

    3,基于构造函数模式的原型模式解决了方法或者属性公有的问题;
    把实例的相同的属性/方法,提取成公有的属性/方法,只要是它的实例对象,都可以获取

    function CreateJsPerson(name,age){//自己创建的类,首字母要大写
        this.name=name;
        this.age=age;
    };
    CreateJsPerson.prototype.writeJs=function(){ // 公有的属性和方法
        console.log("我是prototype的公有属性writeJs");
    };
    

    4,原型链接式
    必须保证中间连接每一个返回的结果都是一个数组,才能使用链接式,不是数组,完成不了下面的链接,会报错;只有数组才能使用我们Array原型上定义的属性和方法

    ary.sort(function(a,b){
        return a-b;
    }).reverse().pop();
    

    5,批量设置原型上的公有属性和方法

    • 起一个别名,把原来原型指向的地址赋值给我们的pro,现在他们操作的是同一个空间内存
    • 重构原型对象的方式
      只有浏览器天生给Fn.prototype开辟的堆内存里面才有constructor,而我们自己开辟的这个堆内存,没有这个属性,所有console.log(f.constructor) 指向就不是Fn而是object
    function Fn(){
        this.x=100;
    };
    Fn.prototype={//----这里给他直接创建一个新的堆内存空间
        constructor:Fn// 这个添加指向,保持和原来一致
        a:function(){},
        b:function(){},
        c:function(){},
        d:function(){},
    };
    var f = new Fn;
    f.a();
    f.b();
    console.log(f.constructor); // 没有处理之前,指向object,所以为了和原来保持一致,我们需要手动的增加constructor的指向
    

    6,函数的三角关系(构造函数,对象,原型对象)

    万物皆对象; 例外:Function.prototype是函数数据类型的值,但是相关操作和之前的一模一样;-----输出的结果是Empty/anonymous(空函数/匿名函数)
    object是Function的儿子;Function的原型是object原型的儿子

    1,函数的三角恋

    • 对象:是通过构造函数创建的
    • 对象的 __proto__属性 = 对象原型
    • 对象.prototyped.constructor = 该构造函数
    • 构造函数.prototype = 该对象原型
      image.png
      所有原型对象的proto都是Object原型对象,proto的最巅峰值是 null,上图中,表示出来了

    2,函数本身一些自己的属性
    length:0-----形参的个数
    name:“Fn”-----函数名
    prototype:类的原型, 在原型上定义都是当前Fn这个实例的公有方法
    __proto__:把函数当做一个普通的对象,指向Function这个类的原型

    3,函数在整个js中,一个函数存在多面性
    本身就是一个普通的函数:执行的时候形成私有的作用域(闭包),形参赋值,预解释,代码执行,执行完成后栈内存销毁/不销毁
    :他有自己的实例,也有一个叫做prototype属性是自己的原型,他的实例都可以指向自己的原型
    普通类型:和 var obj=Fn; 中的 obj 一样,就是一个普通的对象,他作为对象可以有一些自己的私有属性,也可以通过__proto__找到Function.prototype;当然这里如果this指向是window,没有return,那么是undefined

    7,函数中更改this的指向

    这里是介绍三种方法,详情可见https://www.jianshu.com/p/c59b45c519f5

    • apply()
    • call()
    • bind()

    8,面向对象三大特征:封装,继承,多态(抽象)

    1,封装:复制对象的成员给另一个对象

    function  extend(parent,child){
     for(var key in parent){
     if(child[key]){
     continue;}
     child[key]=parent[key]}
    }
    

    2,继承是类型和类型之间的关系
    继承目的:把子类型中共同的成员提取在父类型中,代码重用
    案例:

    <!--都是人,都有name,age,sex这些属性,把这些属性提取出来作为公共的部分  -->
    function  Person(name,age,sex){
       this.name=name
       this.age=age
       this.sex=sex;
    }
    
    <!--这里调用Person()构造函数,但是在Parent中,this指向与Child中的this指向不同,在这里要改变this指向,用call方法改变,不管是Parent与Child都可以用到公共属性 -->
    function  Parent(name,age,sex,salary){
        this.name=name;
        this.age=age;
        this.sex=sex;
    }
    function  Child(name,age,sex,salary){
        Person.call(this,name,age,sex);
        this.salary=salary;
    }
    

    组合继承:借用构造函数 + 原型继承
    //通过原型让子类型继承父类型中的方法

    Teacher.prototype = new Person();//Person是指一个构造函数
    //因为这个是手动设置的,不是浏览器自动赋值的,所以这边constructor指向不是构造函数本身,这边要手动改变
    Teacher.prototype.constructor=Teacher; 
    

    3,多态(这里我还没有整理,整理好,会自动更新)

    9,函数的其他参数

    函数本质上的对象,call,bind都是对象上的方法

    • arguments: 是一个伪数组,获取到的是函数的实参
    function fn(){
        console.log(fn.arguments);// 2,3,4
    }
    
    fn(2,3,4)
    
    • caller:函数的调用者
    function fn(){
        console.log(fn.caller) // test(因为是在test下面调用的)
    }
    
    function test(){
      fn()
    }
    
    • name:函数的名称
    function fn(){
        console.log(fn.name) // fn
    }
    
     fn()
    
    • length:函数的形参个数
      注意这个不是实参个数
    function fn(x, y){
        console.log(fn.length) // 2
    }
    
     fn(3,7,9)
    
    arguments 这里单独介绍一下

    当函数参数个数不固定时候
    在函数内部,我们可以通过arguments来获取实际传过来的参数

    function fn(){
        console.log(arguments);// 2,3,4
    }
    
    fn(2,3,4)
    

    补充:
    this 的指向:https://www.jianshu.com/p/bf8d22a67134
    改变 this 的指向方法:https://www.jianshu.com/p/c59b45c519f5

    相关文章

      网友评论

          本文标题:js 函数

          本文链接:https://www.haomeiwen.com/subject/udzkdhtx.html