JS 系列问题

闭包

引入源 闭包

  • Javascript特殊的变量作用域:全局变量和局部变量
  • 函数内部可以直接读取全局变量;在函数外部自然无法读取函数内的局部变量
  • 为了获取函数内部局部变量,在函数的内部,再定义一个函数
      function f1(){
        var n=999;
        function f2(){
          alert(n); // 999
        }
      }

这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立

既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,就可以在 f1 外部读取它的内部变量

  • 闭包的概念:
    上面的f2函数,就是闭包。
    闭包就是能够读取其他函数内部变量的函数
  • 除了读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中
    其实是闭包函数的一个缺陷(太多闭包会占据太多内存)
      function f1(){
        var n=999;
        nAdd=function(){n+=1}
        function f2(){
          alert(n);
        }
        return f2;
      }
      var result=f1();
      result(); // 999
      nAdd();
      result(); // 1000

由于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收

  • 所以注意:
    1——由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除
    2——闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值

关于闭包里的this

      var name = "The Window";
      var object = {
        name : "My Object",
        getNameFunc : function(){
          return function(){
            return this.name;
          };
        }
      };
      alert(object.getNameFunc()());

这里getNameFunc()函数在调用的时候,其实是在windows环境下调用的,可以看成 var fun = object.getNameFunc()。this 应该为fun所在的环境即全局;
为了解决获取内部this,需要在对象内部属性里保存,如下:

var name = "The Window";
var object = {
    name: "My object",
    getNameFunc: function() {
        var that = this;   // 将getNameFunc()的this保存在that变量中
        var age = 15;
        return function() {
            return that.name;
        };
    }
}
alert(object.getNameFunc()());   // "My object"

原型链

在这里插入图片描述

this

引入源 this

var obj = {
  foo: function () { console.log(this.bar) },
  bar: 1
};

var foo = obj.foo;
var bar = 2;

obj.foo() // 1
foo() // 2
  • 简单来说,对于 obj.foo(),foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境
  • 那函数的运行环境到底是怎么决定的?(这里不进行深入,请点击前面的连接)

快速排序

  • 思想
  1. 在数据集之中,选择一个元素作为"基准"(pivot)
  2. 所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边
  3. 对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止
  • Javascript语言实现
    首先,定义一个quickSort函数,它的参数是一个数组
    var quickSort = function(arr) {
    
    };

然后,检查数组的元素个数,如果小于等于1,就返回。

    var quickSort = function(arr) {
      if (arr.length <= 1) { return arr; }
    };

接着,选择"基准"(pivot),并将其与原数组分离,再定义两个空数组,用来存放一左一右的两个子集

    var quickSort = function(arr) {
      if (arr.length <= 1) { return arr; }
      var pivotIndex = Math.floor(arr.length / 2) ;
      var pivot = arr.splice(pivotIndex, 1)[0];
      var left = [];
      var right = [];
    };

然后,开始遍历数组,小于"基准"的元素放入左边的子集,大于基准的元素放入右边的子集

    var quickSort = function(arr) {
      if (arr.length <= 1) { return arr; }
      var pivotIndex = Math.floor(arr.length / 2) ;
      var pivot = arr.splice(pivotIndex, 1)[0];
      var left = [];
      var right = [];
      for (var i = 0; i < arr.length; i++){
        if (arr[i] < pivot) {
          left.push(arr[i]);
        } else {
          right.push(arr[i]);
        }
      }
    };

最后,使用递归不断重复这个过程,就可以得到排序后的数组

    var quickSort = function(arr) {
      if (arr.length <= 1) { return arr; }
      var pivotIndex = Math.floor(arr.length / 2);
      var pivot = arr.splice(pivotIndex, 1)[0];
      var left = [];
      var right = [];
      for (var i = 0; i < arr.length; i++){
        if (arr[i] < pivot) {
          left.push(arr[i]);
        } else {
          right.push(arr[i]);
        }
      }
      return quickSort(left).concat([pivot], quickSort(right));
    };

模拟类(非语法糖的 class)

  1. 构造函数法
    用构造函数模拟"类",在其内部用this关键字指代实例对象
      function Cat() {
        this.name = "大毛";
      }

生成实例的时候,使用new关键字

      var cat1 = new Cat();
      alert(cat1.name); // 大毛

类的属性和方法,还可以定义在构造函数的prototype对象之上

      Cat.prototype.makeSound = function(){
        alert("喵喵喵");
      }
  1. Object.create()法
    "类"就是一个对象,不是函数
      var Cat = {
        name: "大毛",
        makeSound: function(){ alert("喵喵喵"); }
      };

然后,直接用Object.create()生成实例,不需要用到new

      var cat1 = Object.create(Cat);
      alert(cat1.name); // 大毛
      cat1.makeSound(); // 喵喵喵

目前,各大浏览器的最新版本(包括IE9)都部署了这个方法。如果遇到老式浏览器,可以用下面的代码自行部署

      if (!Object.create) {
        Object.create = function (o) {
           function F() {}
          F.prototype = o;
          return new F();
        };
      }

!!注意:不能实现私有属性和私有方法,实例对象之间也不能共享数据,对"类"的模拟不够全面

  1. 极简主义法

这种方法不使用this和prototype,代码部署起来非常简单

封装

  • 首先,它也是用一个对象模拟"类"。在这个类里面,定义一个构造函数createNew(),用来生成实例
      var Cat = {
        createNew: function(){
          // some code here
        }
      };
  • 然后,在createNew()里面,定义一个实例对象,把这个实例对象作为返回值
      var Cat = {
        createNew: function(){
          var cat = {};
          cat.name = "大毛";
          cat.makeSound = function(){ alert("喵喵喵"); };
          return cat;
        }
      };
  • 使用的时候,调用createNew()方法,就可以得到实例对象
      var cat1 = Cat.createNew();
      cat1.makeSound(); // 喵喵喵

继承

调用后者的createNew()方法即可

  • 先定义一个Animal类
      var Animal = {
        createNew: function(){
          var animal = {};
          animal.sleep = function(){ alert("睡懒觉"); };
          return animal;
        }
      };
  • 然后,在Cat的createNew()方法中,调用Animal的createNew()方法
      var Cat = {
        createNew: function(){
          var cat = Animal.createNew();
          cat.name = "大毛";
          cat.makeSound = function(){ alert("喵喵喵"); };
          return cat;
        }
      };
  • 这样得到的Cat实例,就会同时继承Cat类和Animal类
      var cat1 = Cat.createNew();
      cat1.sleep(); // 睡懒觉

私有属性和私有方法

  • 在createNew()方法中,只要不是定义在cat对象上的方法和属性,都是私有的

数据共享

  • 有时候,我们需要所有实例对象,能够读写同一项内部数据。这个时候,只要把这个内部数据,封装在类对象的里面、createNew()方法的外面即可
      var Cat = {
        sound : "喵喵喵",
        createNew: function(){
          var cat = {};
          cat.makeSound = function(){ alert(Cat.sound); };
          cat.changeSound = function(x){ Cat.sound = x; };
          return cat;
        }
      };
  • 然后,生成两个实例对象:
      var cat1 = Cat.createNew();
      var cat2 = Cat.createNew();
      cat1.makeSound(); // 喵喵喵
  • 这时,如果有一个实例对象,修改了共享的数据,另一个实例对象也会受到影响
      cat2.changeSound("啦啦啦");
      cat1.makeSound(); // 啦啦啦

获取页面位置

从页面大小开始,两种方法:

  • clientHeight和clientWidth属性(不包括滚动条和border占用的空间)[注意下兼容性,有部分浏览器不知道]详情见
    链接
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight

  • 包含滚动条在内的该元素的视觉面积(可以和上面一起用,取最大值 Math.max())
      width: document.documentElement.scrollWidth,
      height: document.documentElement.scrollHeight

绝对位置-获取元素相对于页面左上角的位置 :offsetTop和offsetLeft(表示该元素的左上角与父容器(offsetParent对象)左上角的距离),所以需要两个相加

  function getElementLeft(element){
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;
    while (current !== null){
      actualLeft += current.offsetLeft;
      current = current.offsetParent;
    }
    return actualLeft;
  }

相对位置-指该元素左上角相对于浏览器窗口左上角的坐标
(将绝对坐标减去页面的滚动条滚动的距离就可以了)
滚动条滚动的垂直距离,是document对象的scrollTop属性;滚动条滚动的水平距离是document对象的scrollLeft属性

  function getElementViewTop(element){
    var actualTop = element.offsetTop;
    var current = element.offsetParent;
    while (current !== null){
      actualTop += current. offsetTop;
      current = current.offsetParent;
    }
     if (document.compatMode == "BackCompat"){
      var elementScrollTop=document.body.scrollTop;
    } else {
      var elementScrollTop=document.documentElement.scrollTop;
    }
    return actualTop-elementScrollTop;
  }

获取元素位置的快速方法

  • getBoundingClientRect()方法
    相对位置:
  var X= this.getBoundingClientRect().left;
  var Y =this.getBoundingClientRect().top;

再加上滚动距离,就可以得到绝对位置:

  var X= this.getBoundingClientRect().left+document.documentElement.scrollLeft;
  var Y =this.getBoundingClientRect().top+document.documentElement.scrollTop;

不宜使用的语法

  1. 永远只使用===和!==
  2. switch 贯穿,凡是有case的地方,一律加上break
  3. 单行的块结构
    if、while、do和for,都是块结构语句,但是也可以接受单行命令
    建议不管是否只有一行命令,都一律加上大括号
  4. ++和- -
    表面上可以让代码变得很紧凑,但是实际上会让代码看上去更复杂和更晦涩。因此为了代码的整洁性和易读性,不用为好【这个,,,,】
  5. function语句
  function foo() { }
和
  var foo = function () { }

但是在解析的时候,前一种写法会被解析器自动提升到代码的头部,因此违背了函数应该先定义后使用的要求,所以建议定义函数时,全部采用后一种写法
6. 基本数据类型的包装对象

  new String("Hello World");
  new Number(2000);
  new Boolean(false);

这样写完全没有必要,而且非常费解,因此建议不要使用
——new Object和new Array也不建议使用,可以用{}和[]代替
7. new语句(类)
一种变通方法

  Object.beget = function (o) {
    var F = function (o) {};
    F.prototype = o ;
    return new F;
  };

创建对象时就利用这个函数,对原型对象进行操作

  var Cat = {
    name:'',
    saying:'meow'
  };
  var myCat = Object.beget(Cat);

对象生成后,可以自行对相关属性进行赋值

myCat.name = 'mimi';
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值