《JavaScript高级程序设计》(第三版)学习复盘(二)

457 阅读12分钟

一.函数表达式

定义函数有两种形式:

  • 函数声明
aa();
function aa() {
    console.log(hello word);
};

这个例子不会报错,因为哎代码执行之前会先读取函数声明。关于函数声明最重要的一个特征就是函数声明提升

  • 函数表达式(匿名函数,拉姆达函数)
var aa = function (){
    console.log(hello word);
};

1)递归

递归函数是在一个函数通过名字调用自身的情况下构成的。

function factorial(num){
    if(num <= 1){
        return 1;
    }else {
       return num *  factorial(num - 1);
    }
}
var ano = factorial;
factorial = null;
console.log(ano(4)); // error;
// ano被执行,factorial已经不是函数了就会报错。
//  方法1
function factorial(num){
    if(num <= 1){
        return 1;
    }else {
       return num *  arguments.callee(num - 1);
    }
}
//  方法2
var factorial = (function f(num){
        if(num <= 1){
        return 1;
    }else {
       return num *  f(num - 1);
    }
})();

2)闭包

闭包是指有权访问另一个函数作用域中的变量的函数。

1.闭包与变量

作用域链的这种机制引出一个副作用: 闭包只能取得包含函数中任何变量中的最后一个

2. 关于this

在全局函数中,this等于window,而当函数被当作为某个对象的方法调用的时候。this等于那个对象。

但是在匿名函数的执行具有全局性,因此this通常指向window。但是在对象内部通常指向这个对象。

3.内存泄漏

内部函数被外部函数包含时,内部函数会将外部函数的局部活动对象添加到自己的作用域链中。而由于内部匿名函数的作用域链 在引用 外部包含函数的活动对象,即使outFun执行完毕了,它的活动对象还是不会被销毁!即,outerFun的执行环境作用域链都销毁了,它的活动对象还在内存中留着呢。并且根据垃圾回收机制,被另一个作用域引用的变量不会被回收。

闭包在执行过程中,活动对象会初始化包含此函数的活动对象和全局对象。但是函数在执行完之后,匿名函数的作用域链还在引用这个活动对象。(函数没有被销毁)所以要最使用完函数后要解绑函数引用。

3)模仿块级作用域

js是没有块级作用域的概念的。在这样的情况下闭包可以声明一个块级作用域。

(function(){
    //  这里是块级作用域
    var name = "小明";
})();
//  这样的写法可以减少内存占用。函数执行完毕或立即销毁。

4)私有变量

严格的来讲JavaScript中是每一偶私有成员的概念的;所有对象属性都共有的。但是有一个私有变量的概念。

1.静态只有变量

既然有私有变量那也就会有私有方法和公有(特权)方法。

(function(){
    //  私有变量
    var num = 10;
    //  私有函数
    function numFun() {
        return false;
    };
    function MyObj() {};
    //  公有/特权方法
    MyObj.prototype.publiceMethod = function() {
        num ++;
        return numFun();
    }
})();

2.模块模式

上面定义的是一种自定义类型的模式。还有一种是叫单例模式。所谓单例,指的就是只有一个实例的对象。

var singleton = function() {
        //  私有变量
    var num = 10;
    //  私有函数
    function numFun() {
        return false;
    };
    //  公有/特权方法和属性
    return {
        publice: true,
        publiceMethod: function(){
            num++;
            return numFun(); 
        }
    }
};

3.增强的模块模式

有人改进了模块模式:在返回对象之前加入其增强代码。

      function CustomType() {};
      var singleton = function () {
        //  私有变量
        var num = 10;
        //  私有函数
        function numFun() {
          return false;
        };
        //  实例化方法/创建对象
        var obj = new CustomType();

        //  公有/特权方法和属性
        obj.publice = true;
        obj.publiceMethod = function () {
          num++;
          return numFun();
        };
        //  返回这个对象
        return obj;
      }();
      console.log(singleton.publice);

      function BaseComponents(){};
      var application = function () {
        //  私有变量和函数
        var components = new Array();
        //  初始化
        components.push(new BaseComponents());
        //  创建application的一个局部副本
        var app = new BaseComponents();
        app.getComponentCount = function() {
          return components.length;
        };
        //  公共函数
        app.registerComponent = function(component) {
          if(typeof component === "object") {
            components.push(component);
            console.log(components);
          }else{
            console.error('对象类型异常')
          }
        };
        //  返回对象
        return app;
      }(); 
      var bb = {name: 1};
      console.log(application.getComponentCount());
      application.registerComponent(bb);

二.BOM

在web中使用javascript的话,那么核心就是BOM(浏览器对象模型)。

1)window 对象

BOM的核心对象是window,它表示浏览器的一个实例。相当于ECMAScript中的Global对象。

  • 全局作用域

题外话:用var关键字定义的变量,会自动归在window对象下。如果window对象下有相同的属性,会被覆盖。但是ES6中的const和let不会。

var语句添加的window属性有一个名为[[Configurable]]的特性,通常这个值为false。所以这样定义出来的属性不能为delete删除。

  • 窗口操作
    //  获取窗口相对于屏幕的左边的距离
    var leftPos = (typeof window.screenLeft === "number") ? window.screenLeft : window.screenX;
    //  获取窗口相对于屏幕的上边的距离
    var topPos = (typeof window.screenTop === "number") ? window.screenTop : window.screenY;
    
    //  浏览器显示视图的宽度
    var pageWidth = window.innerWidth;
    //  浏览器显示视图的高度
    var pageHeight = window.innerHeight;
    if(typeof pageWidth !== "number"){
        //  是否是标准模式
        if(document.compatMode === "CCS1Compat"){
          //  现代浏览器
          pageWidth = document.documentElement.clientWidth;
          pageHeight = document.documentElement.clientHeight;
        }else{
          //  浏览器兼容
          pageWidth = document.body.clientWidth;
          pageHeight = document.body.clientHeight;
        }
    }
  //  浏览器本身的宽度
  var outerWidth = window.outerWidth;
  //  浏览器本身的高度
  var outerHeight = window.outerHeight;
  //  导航到一个特定的url
  window.open("https://www.baidu.com/","测试","height=400","width=400","top=10","left=10","resizable=yse");
  • 间接调用和超时调用
// 间接调用和清除
setTimeout(function(){
    // 执行代码
},300)
clearTimeout()
//  超时调用和清除
setInterval(function(){
     // 执行代码
},300)
clearInterval()

javaScript是一个单线程的解释器。因此一定时间只能执行一段代码。执行javaScript代码就以偶一个队列,任务按照它们添加到队列的顺序执行。第二哥参数告诉javaScript多长时间添加到队列。如果队列是空就立即执行,如果不是空,等待前面的代码执行完毕之后在执行。

2)location 对象

// location对象属性
location.hash //("#contents")url的hash,如果url中不包含,则返回空字符串
location.host //  ("www.baidu.com:80") 服务器名称和端口号(如有)
location.hashname //  ("www.baidu.com") 服务器名称(没有端口号)
location.href(location.toString()) // ("https://www.beidu.com")当前加载页面完整的url。
location.pathname // ("/wiley/"") url中目录或文件名
location.port //("8080")url中的端口号,如没有,则返回空字符串
location.protocol //("http")查看页面使用协议。通常是"http"或者"https"
location.search  //("?name=vivian")url查询字符串。这个符号以问号开头

使用上述方法修改url都会在浏览器中生成一条记录。

1.获取字符串参数

  // 获取url头部参数
  function getStringParameter(text = null) {
    let urlText = "";
    if (text) {
      //  截取传入的字符串
      urlText = text.includes("?") ? text.split("?")[1] : "";
    } else {
      //  获取当前页面url并判断是有带有参数
      urlText = location.search.length ? location.search.substring(1) : "";
    }
    //  截取分割的url
    let urlArr = urlText.length ? urlText.split("&") : [];
    //  装载url数据的容器
    let urlObj = {};
    urlArr.forEach((item) => {
      let itemsArr = item.split("=");
      //  处理文字
      let name = decodeURIComponent(itemsArr[0]);
      let value = decodeURIComponent(itemsArr[1]);
      if (name.length) {
        urlObj[name] = value;
      }
    });
    return urlObj;
  };

2.位置操作

显示当前窗口加载的文档信息和一些导航功能。

  window.location = "https://www.baidu.com/";
  location.href = "https://www.baidu.com/";
  location.reload(); //重新加载(可能从缓存加载)
  location.reload(true); // 重新加载(从服务器加载)

3)navigator 对象

用来识别浏览器的标准。

navigator.appCodeName:返回浏览器的代码名。
navigator.appMinorVersion:返回浏览器的次级版本。
navigator.appName:返回浏览器的名称。
navigator.appVersion:返回浏览器的平台和版本信息。
navigator.browserLanguage:返回当前浏览器的语言。
navigator.cookieEnabled:返回指明浏览器中是否启用 cookie 的布尔值。
navigator.cpuClass:返回浏览器系统的 CPU 等级。
navigator.onLine:返回指明系统是否处于脱机模式的布尔值。
navigator.platform:返回运行浏览器的操作系统平台。
navigator.systemLanguage:返回 OS 使用的默认语言。
navigator.userAgent:返回由客户机发送服务器的 user-agent 头部的值。
navigator.userLanguage:返回 OS 的自然语言设置。
navigator.javaEnabled():规定浏览器是否启用 Java。
navigator.taintEnabled():规定浏览器是否启用数据污点 (data tainting)。

// 插件检查
  function hasPlugin(name) {
    const _name = name.toLowerCase();
    console.log(navigator.plugins);
    let leng = navigator.plugins.length;
    // for(let i = 0; i < leng; i+1 ){
    for(let i = 0; i < leng; i+1 ){
      if(navigator.plugins[i].name.toLowerCase().includes(name)){
        return true
      }else {
        return false
      }
    }
  }
  console.log(hasPlugin("Flash"));

4)screen 对象

screen对象用处不大,基本上只是表达客户端能力(而且很多方法被浏览器屏蔽)。

5)history 对象

该对象保存着用户的上网记录。

    //  后退一页
    history.go(-1);
    history.back();
    // 前进一页
    history.go(1);
    history.forward();

三.客户端检测

    //  用户代理字符串检测(可以检测引擎、平台、操作系统、移动设备、游戏平台)
   const client = function() {
      //呈现引擎
      let engine = {
          ie: 0,
          gecko: 0,
          webkit: 0,
          khtml: 0,
          opera: 0,
          // 完整版本号
          ver: null
      };
      // 浏览器
      let browser = {
          // 主要浏览器
          ie: 0,
          firefox: 0,
          safari: 0,
          konq: 0,
          opera: 0,
          chrome: 0,
          // 具体版本号
          ver: null
      };

      // 平台/设备/操作系统
      let system = {
          win: false,
          mac: false,
          x11: false,
          // 移动设备
          iphone: false,
          ipod: false,
          ipad: false,
          ios: false,
          android: false,
          nokiaN: false,
          winMobile: false,
          // 游戏系统
          wii: false,
          ps: false
      };

      // 检测呈现引擎和浏览器
      let ua = navigator.userAgent;
      if (window.opera) {
          engine.ver = browser.ver = window.opera.version();
          engine.opera = browser.opera = parseFloat(engine.ver);
      } else if (/AppleWebKit\/(\S+)/.test(ua)) {
          // \S 匹配一个非空白字符
          engine.ver = RegExp["$1"];
          engine.webkit = parseFloat(engine.ver);
          // 确定是chrome还是safari
          if (/Chrome\/(\S+)/.test(ua)) {
              browser.ver = RegExp["$1"];
              browser.chrome = parseFloat(browser.ver);
          } else if (/Version\/(\S+)/.test(ua)) {
              browser.ver = RegExp["$1"];
              browser.safari = parseFloat(browser.ver);
          } else {
              //近似的确定版本号
              let safariVersion = 1;
              if (engine.webkit < 100) {
                  let safariVersion = 1;
              } else if (engine.webkit < 312) {
                let safariVersion = 1.2;
              } else if (engine.webkit < 412) {
                let safariVersion = 1.3;
              } else {
                  let safariVersion = 2;
              }

              browser.safari = browser.ver = safariVersion;
          }
      } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
          engine.ver = browser.ver = RegExp["$1"];
          engine.khtml = browser.konq = parseFloat(engine.ver);
      } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
          engine.ver = RegExp["$1"];
          engine.gecko = parseFloat(engine.ver);

          // 确定是不是firefox
          if (/Firefox\/(\S+)/.test(ua)) {
              browser.ver = RegExp["$1"];
              browser.firefox = parseFloat(browser.ver);
          }
      } else if (/MSIE ([^;]+)/.test(ua)) {
          engine.ver = browser.ver = RegExp["$1"];
          engine.ie = browser.ie = parseFloat(engine.ver);
      }

      // 检测浏览器
      browser.ie = engine.ie;
      browser.opera = engine.opera;

      // 检测平台
      let p = navigator.platform;
      system.win = p.includes("Win");
      system.mac = p.includes("Mac");
      system.x11 = (p === "x11") || (p.includes("Linux"));

      // 检测window操作系统
      if (system.win) {
          if (/Win(?:dows)?([^do]{2})\s?(\d+\.\d+)?/.test(ua)) {
              if (RegExp["$1"] === "NT") {
                  switch(RegExp["$2"]) {
                      case "5.0":
                          break;
                      case "5.1":
                          system.win = "XP";
                          break;
                      case "6.0":
                          system.win = "Vista";
                          break;
                      case "6.1":
                          system.win = "7";
                          break;
                      default:
                          system.win = "NT";
                          break;
                  }
              } else if (RegExp["$1"] === "9x") {
                  system.win = "ME";
              } else {
                  system.win = RegExp["$1"];
              }
          }
      }

      // 移动设备
      system.iphone = ua.includes("iPhone");
      system.ipod = ua.includes("iPod");
      system.ipad = ua.includes("ipad");
      system.nokiaN = ua.includes("NokiaN");

      // windows mobile
      if (system.win === "CE") {
          system.winMobile = system.win;
      } else if (system.win === "Ph") {
          if (/Windows Phone OS (\d+.\d+)/.test(ua)) {
              system.win = "Phone";
              system.winMobile = parseFloat(RegExp["$1"]);
          }
      }

      // 检测ios版本
      if (system.mac && ua.includes("Mobile")) {
          if (/CPU (?:iphone)?OS (\d+_\d+)/.test(ua)) {
              system.ios = parseFloat(RegExp.$1.replace("_", "."));
          } else {
              system.ios = 2;  //不能正确检测出来,只能猜测
          }
      }

      // 检测android
      if (/Android (\d+\.\d+)/.test(ua)) {
          system.android = parseFloat(RegExp.$1);
      }

      // 游戏系统
      system.wii = ua.includes("Wii");
      system.ps = /playstation/i.test(ua);

      // 返回检测对象
      return {
          engine: engine,
          browser: browser,
          system: system
      }
  }();

四.DOM

早起的IE浏览器的DOM是基于COM实现的,和同时期的浏览器有很大的差异

节点层次

  • 节点关系

每个节点都会有childNodes属性,其中保存着一个NodeList对象(类数组对象)。由此我们可以将NodeList转化为数组对象。但是早期的IE浏览器是基于COM来实现DOM的,所有我们有如下方法:

  function converToArray(nodes) {
    var array = null;
    try{
      //  针对非早期IE浏览器
      array = Array.prototype.slice.call(oBody.childNodes, 0);
    }catch (e){
      //  早期IE检查
      array = new Array();
      for(var i = 0; i < nodes.length; i += 1){
        array.push(nodes[i]);
      }
    }
    return array;
  };

另外我们可以使用hasChildNodes() ,来查看元素下有子节点(有文字也算文字节点):

  console.log(html.hasChildNodes());
  • 操作节点
//  向目标元素末尾插入一个子节点
someNode.appendChild(nodes);
//  在指定位置插入节点
someNode.insertBefore(nodes, someNode.childNodes[ number ]);
//  替换某个节点
someNode.replaceChild(nodes, someNode.lastChild); //  替换最后一个
//  移除某个节点
someNode.removeChild(nodes);
//  找到所有form元素
document.forms
//  找到所有img
dcument.images
//  获取元素上的属性
var oText = someNode.getAttribute('data-ti');
//  设置节点属性(IE8以前不支持设置style和class,不生效)
someNode.setAttribute('data-titit',"title");
//  删除节点属性(IE6不支持)
someNode.removeAttribute("data-ti");
//  用attrValue遍历属性
function outputAttributes(el) {
let pairs = [];
let [attrName, attrValue] = Array(2).fill(null);
let arr = el.attributes;
for (let item of arr) {
  attrName = item.nodeName;
  attrValue = item.nodeValue;
  pairs.push(`${attrName}="${attrValue}"`)
}
return pairs.join(" ");
}
// 创建元素节点
docment.createElement("div");
// 创建文本节点
docment.createTextNode("text");

四.DOM扩展

1)选择符API

// 参数为css选择符,返回与该模式匹配的第一个元素,没有找到返回null
querySelector()
// 返回一个NodeList实例,如果没有找到匹配元素返回NodeLise是空
querySelectorAll()

2)元素遍历

 //  IE9以上
 // 返回子元素的个数
1.childElementCount
 // 指向第一个子元素
2.firstElementChild
 // 指向最后一个子元素
3.lastElementChild
 // 指向前一个同辈元素
4.previousElementSibling 
 // 指向后一个同辈元素
5.nextElementSibling

3)html5

// 返回指定所有元素的NodeList
docment.getElementsByClassName();
// 获取焦点
el.focus();
// 获取焦点的元素,文档加载完成时,是body元素的引用,文档加载期间的值为null
docment.activeElement;
// 确定文档是否获取了焦点
docment.hasFocus();

1.HTMLDocument的变化

document.onreadystatechange = function () {
  switch (document.readyState) {
  case "uninitialized":
    // XML 对象被产生,但没有任何文件被加载,即处于“还未开始载入”。
    break;
  case "loading":
    // 表示文档还在加载中,即处于“正在加载”状态。
    break;
  case "interactive":
    // 文档已经结束了“正在加载”状态,DOM元素可以被访问。
    // 但是像图像,样式表和框架等资源依然还在加载。
    break;
  case "complete":
    // 页面所有内容都已被完全加载.
    break;
    default:
    
}
}

2.插入标记

innerHTML

该属性返回与调用元素的所有子节点对应的HTML标记。(返回元素的所有子集)

outerHTML 该属性返回调用它的元素及所有子节点对应的HTML标记。(返回包含自身的所有元素) insertAdjacentHTML()

// 作为前一个同辈元素被插入
el.insertAdjacentHTML('beforebegin', '<p>hello world</p>');
// 作为第一个子元素被插入
el.insertAdjacentHTML('afterbegin', '<p>hello world</p>');
// 作为最后一个子元素被插入
el.insertAdjacentHTML('beforeend', '<p>hello world</p>');
// 作为后一个同辈元素被插入
el.insertAdjacentHTML('afterend', '<p>hello world</p>');

3.scrollIntoView()

该方法是在HTML元素上调用。通过滚动浏览器或者某个元素,调用的元素出现在视口中。

4)专有扩展

contains()

判断某个节点是否是另一个节点的后代

body.contains(div); // 如果childEl是parentEl的后代,则返回true

五.DOM2和DOM3

1)操作样式表

  • insertRule() 向现有的样式表添加新的css。
var sheet = document.styleSheets[0];
sheet.insertRule("body {background: #fff}",0);
  • addRule()
//  仅对IE有效
var sheet = document.styleSheets[0];
sheet.addRule("body"," {background: #fff}",0);
  • deleteRule(number); 从样式表中删除css,接受的删除是要删除样式的位置。
sheet.deleteRule(0);
  • removeRule(number);
//  仅对IE有效
sheet.removeRule(0);

2)元素大小

1.偏移量

//  获取元素在垂直方向向上占用的空间大小,单位: px。
el.offsetHeight; 
// 获取元素在水平向上占用的大小,单位:px。
el.offsetWidht;
// 获取元素的左外边框至包含元素的左内边框之间的像素距离。
el.offsetLeft;
// 获取元素的上外边框至包含元素的上内边框之间的像素距离。
el.offsetTop;

//  获取某个元素在页面上的左偏移量(跨浏览器兼容)
function getElementLeft(el){
    let actualLeft = el.offsetLeft;
    let current = el.offsetParent;
    while(current !== null){
      actualLeft += current.offsetLeft;
      current = current.offsetParent;
    }
    return actualLeft;
}
// 获取某个元素在页面上的上偏移量(跨浏览器兼容)
function getElementTop(el){
    let actualTop = el.offsetTop;
    let current = el.offsetParent;
    while(current !== null){
      actualTop += current.offsetTop;
      current = current.offsetParent;
    }
    return actualTop;
}

2.滚动区大小

//  获取视口高度
var docHeight = Math.max(document.documentElement.scrollHeight,document.documentElement.clientHeight);
//  获取视口宽度
var docWidth = Math.max(document.documentElement.scrollWidth,document.documentElement.clientWidth);

在混乱模式的IE浏览器下(IE6,IE7)需要用document.body来代替document.documentElement。

var docHeight = Math.max(document.body.scrollHeight,document.body.clientHeight);
var docHeight = Math.max(document.body.scrollWidth,document.body.clientWidth);

3.确定元素大小

// 方法返回是一个具有四个属性 left、top、right、bottom 的 DOMRect 对象。
Element.getBoundingClientRect() 

这个方法返回的其实是6个值left、top、right、bottom、x、y。但是在IE的浏览器下x、y是没有的。详情可以参照MDN的文档:developer.mozilla.org/zh-CN/docs/…

4.遍历DOM

这个功能用的实在是很少没有相关资料。我在晚上找到一篇文章可以参考:www.cnblogs.com/venoral/p/5…

1.NodeIterator

//  创建一个NodeIterator对象
var iterator = document.createNodeIterator(Element,NodeFilter.SHOW_ALL,null,false)

2.Treewalker

//  创建一个Treewalker对象
var walker = document.createTreeWalker(Element,NodeFilter.SHOW_ALL,null,false);