XyyFighting


  • 首页

  • 标签

  • 分类

  • 归档

JavaScript高级程序设计读书笔记13

发表于 2018-01-28 | 分类于 读书笔记 , JavaScript高级程序设计

第13章 事件

  • 事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间

事件流

  • 事件流:从页面中接收事件的顺序
  • IE的事件流是冒泡流
  • Netscape的事件流是捕获流

事件冒泡

  • 事件冒泡:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

事件捕获

  • 事件捕获:不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。
  • 用意在于在事件到达预定目标之前捕获他。
  • 使用事件冒泡,在有特殊需要时再使用事件捕获。

DOM事件流

  • “DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。

事件处理程序

  • 事件处理程序:响应某个事件的函数

HTML事件处理程序

  • 不能在其中使用未经转义的HTML语法字符
  • event变量,可以直接访问事件对象
  • this值等于事件的目标对象
  • 在函数内部,可以像访问局部变量一样访问document及该元素本身的成员
  • 存在的缺点:1.存在时差问题 2.扩展事件处理程序的作用域链在不同浏览器中会导致不同的结果 3.HTML和JS代码紧密耦合

DOM0级事件处理程序

  • 将一个函数赋值给一个事件处理程序属性
  • 优势:1.简单 2.跨浏览器
  • 要使用JS指定时间处理程序,首先必须取得一个操作的对象的引用
  • 使用DOM0级方法指定的时间处理程序被认为是元素的方法,因此是在元素的作用域中运行,程序中的this引用当前元素。
  • 会在事件流的冒泡阶段被处理。
    1
    2
    3
    4
    5
    var btn = document.getElementById("myBtn"); 
    btn.onclick = function() {
    alert(this.id); // "myBtn"
    };
    btn.onclick = null; // 删除事件处理程序

DOM2级事件处理程序

  • 指定事件处理程序:addEventListener()
  • 删除事件处理程序:removeEventListener()
    • 三个参数:要处理的事件名、作为事件处理程序的函数、布尔值(true 表示捕获阶段调用事件处理程序,false 表示冒泡阶段调用事件处理程序
  • 添加多个事件处理程序时,会按照添加他们的顺序触发
  • 移除时传入的参数与添加处理程序时使用的参数相同,使用匿名函数则无法移除。
  • 大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器
  • 最好只需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段
    1
    2
    3
    4
    5
    6
    var btn = document.getElementById("myBtn");
    var handler = function() {
    alert(this.id);
    };
    btn.addEventListener("click", handler, false);
    btn.removeEventListener("click", handler, false); // 删除事件处理程序

IE事件处理程序

  • attachEvent()和detachEvent()
  • 两个参数:事件处理程序名称、事件处理程序函数
  • attachEvent()添加的事件处理程序都会被添加到冒泡阶段
  • addEvenet()的第一个参数是onclick,addEventListener()方法中的第一个参数是click
  • 在IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域:DOM0级中是在其所属元素的作用域中运行,在attachEvent方法中则是在全局作用域中运行。
  • 添加多个事件处理程序时,会按照添加他们的顺序的相反顺序触发
  • 移除时传入的参数与添加处理程序时使用的参数相同,使用匿名函数则无法移除。

跨浏览器的事件处理程序

  • EventUtil对象:用于处理浏览器间的差异
  • addHandler():视情况分别使用 DOM0 级方法、DOM2 级方法或 IE 方法来添加事件
  • removeHandler():移除添加的事件处理程序
  • 要操作的元素、事件名称、事件处理程序函数
  • ​
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 定义EventUtil对象
    var EventUtil = {

    addHandler: function(element, type, handler) {
    if (element.addEventListener) {
    element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
    element.attachEvent("on" + type, handler);
    } else {
    element["on" + type] = handler;
    }
    },
    removeHandler: function(element, type, handler) {
    if (element.removeEventListener) {
    element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
    element.detachEvent("on" + type, handler);
    } else {
    element["on" + type] = null;
    }
    }
    }

    // 使用EventUtil对象
    var btn = document.getElementById("myBtn");
    var handler = function() {
    alert("Clicked");
    }

    EventUtil.addHandler(btn, "click", handler);
    EventUtil.removeHandler(btn, "click", handler);

    EventUtil对象方法定义及用法

事件对象

  • 事件对象event,包含着所有与事件有关的信息。

DOM中的事件对象

  • currentTarget:事件处理程序当前正在处理事件的那个元素。target:事件的目标
  • 在事件处理程序内部,对象this始终等于currentTarget的值,而target则值包含事件的实际目标。
  • 如果直接将事件处理程序制定给了目标元素,则this、currentTarget、target包含相同的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var btn=document.getElementById("myBtn");
    btn.onclick=function(event){
    alert(event.currentTarget); //[object HTMLInputElement]
    alert(this); //[object HTMLInputElement]
    alert(event.target); //[object HTMLInputElement]
    alert(event.currentTarget===this); //true
    alert(event.target===this); //true
    }


    document.body.onclick=function(event){
    alert(event.currentTarget); //[object HTMLBodyElement]
    alert(this); //[object HTMLBodyElement]
    alert(event.target); //[object HTMLInputElement]
    }
  • type属性:需要通过一个函数处理多个事件时

  • preventDefault方法:阻止特定事件的默认行为
  • stopPropagation():用于立即停止事件在DOM层次中的传播

    1
    2
    3
    4
    5
    6
    7
    8
    var btn=document.getElementById("myBtn");
    btn.onclick=function(event){
    alert("ckicked");
    event.stopPropagation();
    }
    document.body.onclick=function(event){
    alert("Body clicked"); //不会执行
    }
  • eventPhase等于2时,this、target、currentTarget始终都是相等的

  • 只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完成,event对象就会被销毁

IE中的时间对象

  • 在使用DOM0级添加事件处理程序时,event对象作为window对象的一个属性存在window.event
  • 在用attachEvent()添加时,会有一个event对象作为参数被传入事件处理程序函数中,也可以作为window对象的一个属性访问window.event
  • 在通过HTML特性指定的事件处理程序中,通过一个名叫event的变量来访问event对象

跨浏览器的事件对象

  • 定义EventUtil对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    // 定义EventUtil对象
    var EventUtil = {
    //添加事件处理程序
    addHandler: function(element, type, handler) {
    if (element.addEventListener) {
    element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
    element.attachEvent("on" + type, handler);
    } else {
    element["on" + type] = handler;
    }
    },
    //返回对 event 对象的引用
    getEvent: function(event) {
    return event ? event : window.event;
    },
    //返回事件的目标
    getTarget: function(event) {
    return event.targete || event.srcElement;
    },
    //用于取消事件的默认行为
    preventDefault: function(event) {
    if (event.preventDefault) {
    event.preventDefault();
    } else {
    event.retrunValue = false;
    }
    },
    //删除事件处理程序
    removeHandler: function(element, type, handler) {
    if (element.removeEventListener) {
    element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
    element.detachEvent("on" + type, handler);
    } else {
    element["on" + type] = null;
    }
    },
    //阻止事件流
    stopPropagation: function(event) {
    if (event.stopPropagation) {
    event.stopPropagation();
    } else {
    event.cancleBubble = true;
    }
    }
    }
  • 使用EventUtil对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 使用EventUtil对象
    var btn = document.getElementById("myBtn");
    var handler = function() {
    alert("Clicked");
    }

    EventUtil.addHandler(btn, "click", handler);

    btn.onclick = function(event) {
    event.EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    EventUtil.preventDefault(event);
    EventUtil.stopPropagation(event);
    }

    EventUtil.removeHandler(btn, "click", handler);

事件类型

UI事件

  • UI事件:不一定与用户操作有关的事件

load事件

  • 当页面完全加载后(包括所有图像,JS文件,CSS文件等外部资源),就会触发window上面的load事件。
  • 有两种定义onload事件处理程序的方式:1.使用JS代码 2.为body元素添加一个onload特性。尽量使用JS代码
  • 图像、<script>、<link>元素也支持load事件

unload事件

  • 在文档被完全卸载后触发,只要用户从一个页面切换到另一个页面就会发生unload事件
  • 利用unload事件多用于清除引用,避免内存泄漏
  • 有两种定义onunload事件处理程序的方式:1.使用JS代码 2.为body元素添加一个onunload特性。尽量使用JS代码

resize事件

  • 当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件
  • 有两种定义resize事件处理程序的方式:1.使用JS代码 2.为body元素添加一个onresize特性。尽量使用JS代码
  • 不同的浏览器有不同的触发resize事件机制

scroll事件

  • 虽然是在window对象上发生的,但是实际表示的则是页面中相应元素的变化。
  • 在混杂模式下,通过<body>元素的scrollLeft和scrollTop监控这一变化,在标准模式下,除Safari之外的所有浏览器都会通过<html>元素访问

焦点事件

  • 在页面元素获得或失去焦点时触发
  • focus和blur是主要方法,但是它们都不冒泡

鼠标和滚轮事件

  • click:用户单机主鼠标按钮或者按下回车键触发
  • dblclick:用户双击主鼠标按钮时触发
  • mousedown:在用户按下了任意鼠标按钮时触发。
  • mouseenter:在鼠标光标从元素外部首次移动到了元素范围内触发。
  • mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。
  • mousemove:当鼠标指针在元素内部移动时重复地触发。
  • mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。
  • mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内触发
  • mouseup:在用户释放鼠标按钮时触发
  • 除了mouseenter和mouseleave,所有鼠标事件都会冒泡

客户区坐标位置

  • 事件发生时鼠标指针在视口中的水平和垂直坐标
    1
    2
    3
    4
    5
    var div = document.getElementById("myBtn");
    EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert("Client coordinates: " + event.clientX + "," + event.clientY);
    });

页面坐标位置

  • 事件发生时鼠标指针在页面中的位置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var div = document.getElementById("myDiv");
    EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    var pageX = event.pageX,
    pageY = event.pageY;
    if (pageX === undefined){
    pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
    }
    if (pageY === undefined){
    pageY = event.clientY + (document.body.scrollTop || document.documentElement.scrollTop);
    }
    alert("Page coordinates: " + event.pageX + "|" + pageX + "," + event.pageY + "|"+pageY);
    });

屏幕坐标位置

  • 事件发生时鼠标指针在电脑屏幕中的位置
    1
    2
    3
    4
    5
    var div = document.getElementById("myDiv");
    EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert("Screen coordinates: " + event.screenX + "," + event.screenY);
    });

修改键

  • 按下鼠标时键盘上的某些键的状态也可以影响到所有采取的操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var div = document.getElementById("myDiv");
    EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    var keys = new Array();
    if (event.shiftKey){
    keys.push("shift");
    }
    if (event.ctrlKey){
    keys.push("ctrl");
    }
    if (event.altKey){
    keys.push("alt");
    }
    if (event.metaKey){
    keys.push("meta");
    }
    //将检测到的键的信息显示给用户
    alert("Keys: " + keys.join(","));
    });

相关元素

  • 对于mouseover事件,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。
  • 对于mouseout事件,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素。
  • 跨浏览器取得相关元素,方法添加到EventUtil中
    1
    <div id="myDiv" style="background-color:red;height:100px;width:100px">Move the mouse from here to the white</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
getRelatedTarget: function(event){
if (event.relatedTarget){
return event.relatedTarget;
} else if (event.toElement){
return event.toElement;
} else if (event.fromElement){
return event.fromElement;
} else {
return null;
}
}

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mouseout", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var relatedTarget = EventUtil.getRelatedTarget(event);
alert("Moused out of " + target.tagName + " to " + relatedTarget.tagName);
});

鼠标按钮

  • 只有在主鼠标按钮被单击(或键盘回车键被按下)时才会触发click事件,因此检测按钮的信息并不是必要的
  • DOM的button属性可能有如下三个值:0表示主鼠标按钮,1表示中间的按钮,2表示次鼠标按钮。
  • IE8及之前版本也提供了button属性,但属性的值与DOM的button属性有很大差异。
  • 跨浏览器地确定Button值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    getButton: function(event){
    if (document.implementation.hasFeature("MouseEvents", "2.0")){
    return event.button;
    } else {
    switch(event.button){
    case 0://没有按下按钮
    case 1://主按钮
    case 3://同时按下主、次按钮
    case 5://同时按下主鼠标和中间的鼠标按钮
    case 7://同时按下三个按钮
    return 0;
    case 2://按下次鼠标按钮
    case 6://同时按下次鼠标和中间鼠标按钮
    return 2;
    case 4: return 1;//按下了中间鼠标按钮
    }//以上代码表示把主按钮优先级调到最高,次按钮其次,中间按钮最后
    }
    },

更多的事件信息

  • event对象中还提供了detail属性,用于给出有关事件的更多信息
  • 在同一个元素上相继发生一次mousedown和mouseup事件算作一次单击。
  • 如果鼠标在mousedown和mouseup之间移动了位置,则detail会被重置为0。

鼠标滚轮事件

  • mousewheel事件:用户通过鼠标滚轮与页面交互、在垂直方向上滚动页面时会触发
  • 这个事件可以在任何元素上面触发,最终会冒泡到document或window对象

触摸设备

  • 不支持dblclick事件
  • 轻击可单击元素会触发mousemove事件。
  • mousemove事件也会触发mouseover和mouseout事件。
  • 两个手指放在频幕上且页面随手指滚动而滚动时会触发mousewheel和scroll事件

键盘与文本事件

  • 对键盘事件的支持主要遵循的是DOM0级
  • 用户按了一下键盘上的字符键时,首先会触发keydown事件,然后紧跟着是keypress事件,最后会触发keyup事件。如果用户按着字符键不放,会重复触发keydown和keypress事件,直到用户松开该键为止。
  • 如果按下的是非字符键,首先会触发keydown事件,然后是keyup事件。如果按住这个非字符键不放,就会一直触发keydown事件直到用户松开这个键。

键码

  • 在发生keydown或keyup事件时,event对象的keyCode属性会包含一个代码,与键盘上一个特定的键对应。
  • keyCode对应键码,其中数字字母字符键的keycode属性的值与ASCII码中对应小写字母或数字的编码相同

字符编码

  • 发生keypress事件意味着按下的键会影响到屏幕中文本的显示
  • charCode属性只有在发生keypress事件时才包含值,是按下的那个键所代表字符的ASCII编码。

DOM3级变化

  • key和char属性
  • keyIdentifier 返回U+0000这类字符串
  • location属性,0表示默认键盘,1表示左侧键盘,2表示右侧位置,3表示数字小键盘,4表示移动设备键盘,5表示手柄。在IE9中支持。Safari和Chrome中支持名为keyLocation的等价属性。
  • getModifierState()方法:检测修改值

textInput事件

  • textInput事件:当用户在可编辑区域中输入字符时,就会触发这个事件。
  • 这个用于替代keypress的textInput事件的行为稍有不同。
  • 区别之一就是任何可以获得焦点的元素都可以出发keypress事件,但只有可编辑区域才能出发textInput事件。
  • 区别之二是textInput事件只会在用户按下能够输入实际字符的键时才会被触发,而keypress事件则是那些能影响文本显示的键时也会触发。
  • textInput事件主要考虑的是字符,它的event对象中还包含一个data属性,这个属性的值就是用户输入的字符(而非ASCII值)。
  • event对象上还有一个属性,叫inputMethod,表示把文本输入到文本框中的方式。

复合事件

  • 是DOM3级事件中新添加的一类事件,用于处理IME的输入序列。
  • IME:输入法编辑器,可以让用户输入在物理键盘上找不到的字符

变动事件

  • DOM2级变动事件能在DOM中的某一部分发生变化时给出提示

删除节点

  • 在使用removeChild()或replaceChild()从DOM中删除节点时触发的事件顺序如下:
  • 1.DOMNodeRemoved事件,event.target是被删除的节点,relateNode是目标节点的父节点。冒泡
  • 2.目标节点的子节点触发DOMNodeRemovedDocument事件,不冒泡
  • 3.在父节点上触发DOMSubtreeModified事件

插入节点

  • 在使用appendChild()、replaceChild()、insertBefore()从DOM中插入节点时触发的事件顺序如下:
  • 1.DOMNodeInserted事件,event.target是被插入的节点,relateNode是目标节点的父节点。冒泡
  • 2.目标节点的子节点触发DOMNodeInsertedDocument事件,不冒泡
  • 3.在父节点上触发DOMSubtreeModified事件

HTML5事件

  • DOM规范没有涵盖所有浏览器支持的所有事件,HTML详尽列出了浏览器应该支持的所有事件
  • contextmenu:用以表示何时应该显示上下文菜单,以便开发人员取消默认的上下文菜单,而提供自定义的菜单
  • beforeunload:在浏览器卸载页面之前触发,通过它来取消写在并继续使用原有的网页
  • DOMContentLoaded:在形成完整的DOM树之后就会触发,不理会图像、JS文件、CSS文件或其他资源是否已经加载完毕。始终在load事件之前触发
  • readystatechange:提供与文档或元素的加载状态有关的信息,但这个事件的行为有时候也很难预料。
  • pageshow:在页面显示时触发。新加载的页面,在load事件触发后触发,来自bfcache(往返缓存)的页面,页面状态完全恢复后触发。必须将事件处理程序添加到window
  • pagehide:在浏览器卸载页面时触发。新加载的页面,在unload事件触发后触发,页面状态完必须将事件处理程序添加到window
  • hashchange:在URL参数列表发生变化时通知开发人员

设备事件

  • oritentationonchange:用户查看设备的模式从横向切换为纵向或纵向切换为横向
  • MozOritentation:当设备的加速计检测到设备的方向改变时,就会触发这个事件,告诉开发人员设备在如何移动
  • deviceorientation:加速计检测到设备方向变化时再window对象上触发,告诉开发人员设备在空间中朝哪儿
  • devicemotion:告诉开发人员设备什么时候移动

触摸和手势事件

触摸事件

  • touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。
  • touchmove:当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用preventDefault()可以阻止滚动。
  • touchend:当手指从屏幕上移开时触发。
  • touchcancel:当系统停止跟踪触摸时触发。关于此事件的确切触发时间,文档中没有明确说明。
  • 除了常见的DOM属性外,触摸事件还包含下列三个用于跟踪触摸的属性。
  • touches:表示当前跟踪的触摸操作的Touch对象的数组。
  • targetTouches:特定于事件目标的Touch对象的数组。
  • changeTouches:表示紫上次触摸以来发生了什么改变的Touch对象的数组。

手势事件

  • gesturestart:当一个手指以及按在屏幕上而另一个手指又触摸屏幕时触发。
  • gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发。
  • gestureend:当任何一个手指从屏幕上面移开时触发。

内存和性能

  • 存在的问题:在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须实现指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交换就绪时间。

事件委托

  • 对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
  • 与传统做法相比具有的优势:

    1.document对象很快就可以访问到,而且可以在页面生命周期的任何时点上为它添加事件处理程序(无需等待DOMContentLoaded或load事件)。换句话说,只要可单击的元素呈现在页面上,就可以立即具备适当的功能。
    2.在页面中设置事件处理程序所需的时间更少。只添加一个事件处理程序所需的DOM引用更少,所花的时间也更少。
    3.整个页面占用的内存空间更少,能够提升整体性能。

  • 最适合采用事件委托技术的事件包括:click、mousedown、mouseup、keydown、keyup和keypress。

移除事件处理程序

  • 内存中留有那些过时不用的“空事件处理程序”,也是造成Web应用程序内存与性能问题的主要原因。
  • 两种情况造成“空事件处理程序”:

    1.从文档中移除带有事件处理程序的元素时,例如使用removeChild()和replaceChild()方法,但更多地是发生在使用innerHTML替换页面中某一部分的时候。
    解决方法:(1)手工移除事件处理程序 (2)事件委托,把事件处理程序指定给最高层次的元素,同样能够处理该区域中的事件。
    2.卸载页面中的时候,如果在页面被卸载之前没有清理干净事件处理程序。那它们就会滞留在内存中。
    解决方法:通过onunload事件处理程序移除所有事件处理程序

模拟事件

DOM中的事件模拟

  • 可以在document对象上使用createEvent()方法创建event对象。
  • 在创建了event对象之后,还需要使用与事件有关的信息对其进行初始化。每种类型的event对象都有一个特殊的方法,为它传入适当的数据就可以初始化该event对象。
  • 模拟事件的最后一步就是要触发事件。这一步需要使用dispatchEvent()方法,所有支持事件的DOM节点都支持这个方法。

IE中的事件模拟

  • document.createEventObject()方法可以在IE中创建event对象。但与DOM方式不同的是,这个方法不接受参数,结果会返回一个通用的event对象。
  • 然后,你必须手工为这个对象添加所有必要的信息(没有方法来辅助完成这一步骤)。
  • 最后一步就是在目标上调用fireEvent()方法,这个方法接受两个参数:事件处理程序的名称和event对象。在调用fireEvent()方法时,会自动为event对象添加srcElement和type属性;其他属性则都是必须通过手工添加的。

JavaScript高级程序设计读书笔记12

发表于 2018-01-26 | 分类于 读书笔记 , JavaScript高级程序设计

第12章 DOM2和DOM3

DOM变化

  • 可以通过下列代码来确定浏览器是否支持这些DOM模块
    1
    2
    3
    4
    5
    var supportsDOM2Core = document.implementation.hasFeature("Core", "2.0");
    var supportsDOM3Core = document.implementation.hasFeature("Core", "3.0");
    var supportsDom2HTML = document.implementation.hasFeature("HTML", "2.0");
    var supportsDOM2Views = document.implementation.hasFeature("Views", "2.0");
    var supportsDOM2XML = document.implementation.hasFeature("XML", "2.0");

针对XML命名空间的变化

  • HTML不支持XML命名空间,单XHTML支持XML命名空间。

其他方面的变化

DocumentType类型的变化

  • DocumentType类型新增了3个属性:publicId、systemId和internalSubset.
  • publicId、systemId表示文档类型声明中的两个信息段,在DOM1中无法访问。

    Document类型的变化

  • Document类型的变化中唯一与命名空间无关的方法是importNode()。
  • 用途:从文档中取得一个节点,然后将其导入另一个文档,使其成为文档结构的一部分。
  • 注意:每个节点都有一个ownerDocument属性,表示所属文档,如果调动appendChild()是传入的节点属于不同的文档

    Node类型的变化

  • Node类型中唯一与命名空间无关的变化,为添加了isSupported()方法。与DOM1级document.implementation引入的hasFeature()方法类似。
  • isSupported()方法用于确定当前节点具有什么能力,接受两个参数:特性名和特性版本号。

    框架的变化

  • 框架和内嵌框架分别用HTMLFrameElement和HTMLIFrameElement表示,在DOM2中新增contentDocument(包含一个指针,指向表示框架内容的文档对象)

样式

访问元素的样式

  • 任何支持style特性的HTML元素在JavaScript中都有一个对应的style属性,这个style对象是CSSStyleDeclaration的实例,包含着通过HTML的style特性指定的所有样式信息,但不包含与外部样式表或嵌入样式表经层叠而来的样式。
  • 支持style特性的HTML元素在JavaScript中都有一个对应的style属性。对于使用短划线的css属性,必须将其转换成驼峰大小写形式。
  • 多数情况下,都可以通过简单地转化属性名的格式来实现转换。其中一个不能直接转换的CSS属性就是float。
  • 由于float是JavaScript中的保留字,因此不能用作属性名。

操作样式表

  • insertRule():创建规则。参数:规则文本和表示在哪里插入规则的索引。
  • deletRule():删除规则。参数:要删除的规则位置。

元素大小

  • 偏移量:元素在屏幕上占用的所有可见的空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function getElementLeft(element){
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;

    while(current !== null){
    actualLeft += current.offsetLeft;
    current = current.offsetParent;
    }
    return actuaLeft;
    }

    function getElementTop(element){
    var actualTop += current.offsetTop;
    var current = current.offsetParent;

    while (current != null){
    actualTop += current.offsetTop;
    current = current.offsetParent;
    }
    return actualTop;
    }
  • 客户区大小(client dimension),指的是元素内容及其内边距所占的空间大小。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function getViewport(){
    // 检查document.compatMode属性,以确定浏览器是否运行在混杂模式
    if (document.compatMode == "BackCompat"){
    return(
    width: document.body.clientWidth,
    height: document.body.clientHeight
    );
    }else{
    return{
    //documentElement 属性可返回文档的根节点
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
    };
    }
    }
  • 滚动大小: 包含滚动内容的元素的大小。

  • scrollHeight 在没有滚动条的情况下,元素内容的高度
  • scrollWidth 在没有滚动条的情况下,元素内容的总宽度
  • scrollLeft 隐藏在内容区域左侧的像素数
  • scrollTop 隐藏在内容区域上方的像素数
  • 在确定文档的总高度时(包括基于视口的最小高度),必须取得scrollWidth/clientWidth和scrollHeight/clientHeight中的最大值,才能保证在跨浏览器的环境下得到精确的结果:

    1
    2
    3
    4
    //对于运行在混杂模式下的IE,则需要用document.body代替document.documentElement
    var docHeight = Math.max(document.documentElement.scrollHeight, documentElement.clientHeight);

    var docWidth = Math.max(document.documentElement.scrollWidth, documentElement.clientWidth);
  • 确定元素大小,在IE8及以前认为文档左上角坐标是(2,2),其他浏览器包括IE9则将传统的(0,0)作为起点坐标。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    function getBoundingClientRect(element){

    var scrollTop = document.documentElement.scrollTop;
    var scrollLeft = document.documentElement.scrollLeft;

    if(element.getBoundingClientRect(element)){
    var temp = document.createElement("div");
    temp.style.cssText = "position:absolute; left:0; top:0;";
    document.body.appendChild("temp");

    arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;

    document.body.removeChild(temp);
    temp = null;
    }

    var rect = element.getBoundingClientRect();
    var offset = arguments.callee.offset;

    return(
    left: rect.left + offset,
    right: rect.right + offset,
    top: rect.top + offset,
    bottom: rect.bottom + offset,
    );
    }else{
    var actualLeft = getElementLeft(element);
    var actualTop = getElementTop(element);

    return{
    left: aotualLeft - scrollLeft,
    right: actualLeft + element.offsetWidth - scrollLeft,
    top: actualTop - scrollTop,
    bottom: actualTop + element.offsetHeight - scrollTop
    }
    }

遍历

  • NodeIterator(迭代节点)和TreeWalker;这两个类型能够基于给定的起点对DOM结构执行深度优先(depth-first)的遍历操作。

NodeIterator

  • 可以使用document.createNodeIterator()方法创建新的实例,接收4个参数。
    • root:想要作为搜索起点的树中的节点
  • whatToShow:表示要访问哪些节点的数字代码
  • filter:是一个NodeFilter对象,或者一个表示应该接受还是拒绝某种特定节点的函数
  • entityRefrenceExpansion:布尔值,表示是否要扩展实体引用。在HTML页面中没用,因为其中的实体引用不能扩展。
  • 如何创建一个只显示p元素的节点迭代器
    1
    2
    3
    4
    5
    6
    7
    8
    var filter = {acceptNode: function(){
    return node.tageName.toLowerCase() == "p"?
    NodeFilter.FILTER_ACCEPT:
    NodeFilter.FILTER_SKIP;
    }
    };

    var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);

TreeWalker

  • 比NodeIterator更高级的版本。除了nextNode()和previousNode()在内的相同功能之外还提供其他方法。
    • parentNode():遍历到当前节点的父节点;
    • firstChild():遍历当前节点的第一个子节点;
  • lastChild():遍历当前节点的最后一个子节点;
  • nextSibling():遍历当前节点的下一个同辈节点;
  • previousSibling():遍历当前节点的上一个同辈节点;
  • TreeWalker真正强大的地方在于能够在DOM结构中沿任何方向移动,即使不使用过滤器也可以得到想要的元素.

范围

DOM中的范围

  • createRange()方法创建范围。var range = document.createRange();

简单选择

  • selectNode() 选择整个节点
  • selectNodeContents() 选择节点的子节点

复杂选择

  • setStart()和setEnd()方法:接受两个参数,一个是参照节点,一个是偏移量

操作DOM范围中的内容

  • deleteContents(),删除范围中的内容。调用方式:range.deleteContents();
  • extractContents(),从文档区域移除范围的内容,返回范围内容的文档片段,可以对文档片段进行进一步操作,调用方式:var fragment=range.extractContents();
  • cloneContents(),创建一个文档片段,保存范围的副本,可以对文档片段进行进一步操作,调用方式:var fragment=range.cloneContents();

插入DOM范围中的内容

  • 除了向范围内部插入内容之外,还可以环绕范围插入内容,此时要使用surroundContents()方法。这个方法接收一个参数,即环绕范围内容的节点。

折叠DOM范围

  • 范围折叠就是指范围中未选择文档的任何部分。通俗的描述就是当你选择范围内的文本,点击左键的时候,光标会落在范围的开始或者范围的结束。
  • 使用collapse()进行折叠,接收一个参数,一个布尔值,表示要折叠到范围的那一端,true表示范围的起点,false表示范围的终点。要确定范围已经折叠外币,可以用collapsed属性检查。

比较DOM的范围

  • 在有多个范围的情况下,可以使用compareBoundaryPoints()方法来确定这些范围是否有公共边界。
  • 这个方法接收两个参数:比较方式的常量值和要比较的范围。

复制DOM的范围

  • 可以使用cloneRange()方法复制范围,返回范围的一个副本var newRange=range.cloneRange();

清理DOM范围

  • 在使用完范围之后,最好调用detach()方法,清理范围,回收内存.
    range.detach();// 从文档中分离
    range=null;// 解除引用

JavaScript高级程序设计读书笔记11

发表于 2018-01-25 | 分类于 读书笔记 , JavaScript高级程序设计

第11章 DOM扩展

  • Selectors API和HTML5

选择符API

  • Selectors API致力于让浏览器原生支持CSS查询

querySelector()方法

  • querySelector()方法:接收一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,就返回null。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //取得body元素
    var body=document.querySelector("body");

    //取得ID为mydiv的元素
    var myDiv=document.querySelector("#myDiv");

    //取得类为“Selected”的第一个元素
    var selected = document.querySelector(".selected");

    //取得类为button的第一个图像元素
    var img=document.querySelector("img.button");
  • 通过Document类型调用querySelector方法,会在文档元素的范围内查找匹配的元素

  • 通过Element类型调用的querySelector方法,会在该元素后代元素的范围内查找匹配的元素

querySelectorAll()方法

  • querySelectorAll():返回一个NodeList实例
  • 延伸:querySelectorAll()与getElementBy系列的区别

    1.query选择符选出的元素及元素数组是静态的,getElement方法选出的元素是动态的。
    2.querySelectorAll 方法接收的参数是一个 CSS 选择符。而 getElementsBy 系列接收的参数只能是单一的className、tagName 和 name。
    3.getElementBy系列比querySelectorAll()性能好很多
    4.如果只要一次查找就可得到元素时,首选getElementBy系列,如果需要经过多级查找,才能得到元素时,首选querySelector。

matchsSelector()方法

  • 如果调用元素与该选择符匹配,返回true,否则,false

元素遍历

  • 问题:IE与其他浏览器在返回子节点时会因文本节点是否返回而存在差异,IE不返回,其他版本返回。
  • 解决方式:为DOM元素添加以下属性,不必担心空白的文本节点
  • childElementCount:返回子元素的个数。(不包括文本节点和注释)
  • firstElementChild:指向第一个子元素。firstChild的元素版。
  • lastElementChild:指向最后一个子元素。lastChild的元素版。
  • previousElementSilbing:指向前一个同辈元素。previousSibling的元素版。
  • nextElementSibling:指向后一个同辈元素。nextSibling的元素版。

HTML5

与类相关的扩充

getElementsByClassName()

  • getElementsByClassName():接收一个参数,即一个包含一或多个类名的字符串,返回带有指定类的所有元素的NodeList。

    1
    2
    3
    4
    5
    //取得类中包含“username”和“current”的元素,类名的先后顺序不重要。
    var allCurrentUsernames = document.getElementsByClassName("current username");

    //取得ID为“myDiv”的元素中带有类名“selected”的所有元素:
    var selected = document.getElementById("myDiv").getElementsByClassName("selected");
  • 调用这个方法时,只有位于调用元素子树中的元素才会返回。

classList属性

  • 问题:在操作类名时,需要通过className属性添加、删除和替换类名、因为className中是一个字符串,所以即使只修改字符串一部分,也必须每次都设置整个字符串的值,如删除其中一个类或者添加一个类,操作都十分麻烦。
    <div class="bd user disabled">...</div>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //删除user类
    //取得类名字符串并拆分成数组
    var classNames = div.className.split(/\s+/);
    //找到要删的类名
    var pos = -1,
    i,
    len;
    for(i = 0, len = classNames.length; i < len; i++){
    if(classNames[i] == "user"){
    pos = i;
    break;
    }
    }
    //删除类名
    classNames.splice(i,1);
    //把剩下的类名拼成字符串并重新设置
    div.className = classNames.join(" ");
  • 解决方式:为所有元素添加classList属性
    div.classList.remove("user");

  • add(value):将指定的字符串值添加到列表中,如果值已经存在,就不添加了。
  • contains(value):表示列表中是否存在给定的值,如果存在则返回true,否则返回false。
  • remove(value):从列表中删除给定的字符串
  • toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //删除"disabled"类
    div.classList.remove("disabled");
    //添加"添加current"类
    div.classList.toggle("user");
    //确定元素中是否包含既定的类名
    if(div.classList.contain("bd") && !div.classList.contains("disabled")){
    //执行操作
    }
    //迭代类名
    for(var i = 0,len = div.classList.length;i < len; i++){
    doSomething(div.classList[i]);
    }

焦点管理

  • document.activeElement:始终都会引用DOM中当前获得了焦点的元素。
  • document.hasFocus():用于确定文档是否获得了焦点。
  • 以上的属性和方法最重要的用途就是提高web应用的无障碍性。无障碍web应用的一个主要标志就是恰当的焦点管理。

HTMLDocument的变化

  • readyState属性:通过它来实现一个指示文档已经加载完成的指示器
  • 可能的取值

    loading:正在加载文档
    complete:已经加载完文档

  • compatMode属性:区分渲染页面的模式是标准的还是混杂的
  • 可能的取值

    CSS1Compat:标准
    BackCompat:混杂

  • head属性:var head = document.head || document.getElementsByTagName("head")[0];

字符集属性

  • charset属性:表示文档中实际使用的字符集。
  • defaultCharset:表示根据默认浏览器及操作系统的设置

自定义数据属性

  • HTML5规定可以为元素添加非标准的属性,但要添加前缀data-,目的是为元素提供与渲染无关的信息,或者提供语义信息。
  • <div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>
  • 可以通过元素的dataset属性来访问自定义属性的值。dataset属性的值是DOMStringMap的一个实例,也就是一个名值对的映射。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var div = document.getElementById("myDiv");
    //取得自定义属性的值
    var appId = div.dataset.appId;
    var myname = div.dataset.myname;
    //设置值
    div.dataset.appId = 23456;
    div.dataset.myname = "Nicholas";
    if(div.dataset.myname){
    console.log("Hello, "+ div.dataset.myname);
    }

插入标记

innerHTML属性

  • 读模式:返回调用元素的所有子节点的HTML标记。(包括元素、注释、文本节点)
  • 写模式:根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点。
  • 为innerHTML设置HTML字符串后,浏览器会将这个字符串解析为相应的DOM树。因此设置了innerHTML后,再从中读取HTML字符串,会得到与设置时不一样的结果。原因在于返回的字符串是根据原始HTML字符串创建的DOM数经过序列化之后的结果。

outerHTML

  • 读模式:返回调用它的元素及所有子节点的HTML标签
  • 写模式:会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素。
  • div.outerHTML = "<p>this is paragraph</p>";新创建的< p>元素会取代DOM树中的div元素。

insertAdjacentHTML()

  • insertAdjacentHTML():插入标记
  • 接收两个参数,插入位置和要插入的HTML文本。
  • beforebegin 在当前元素之前插入一个紧邻的同辈元素
  • afterbegin 在当前元素之下插入一个新的子元素或者在第一个子元素之前再插入新的子元素
  • beforeend 在当前元素之下插入一个新的子元素或者在最后一个子元素之后再插入新的子元素
  • afterend 在当前元素之后插入一个紧邻的同辈元素。

内存与性能问题

  • 在删除带有时间处理程序或者引用了其他JavaScript对象子树时,元素与事件处理程序之间的绑定关系在内存中并没有一并删除。在使用innerHTML、outerHTML和insertAdjacentHTML()方法时,最好先手工删除要被替换的元素的所有事件处理程序和JavaScript对象属性。
  • 因此,在使用innerHTML、outerHTML和insertAdjacentHTML()之前,最好先手工删除要被替换的元素的所有事件处理程序和js对象属性。
  • 使用innerHTML效率明显高,这是由于使用属性时创建的解释器是在浏览器级别的代码基础上运行的。

scrollIntoview()

  • 如果给这个方法传入true作为参数,或者不传入任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐。
  • 如果传入false作为参数,调用元素会尽可能全部出现在视口中,(可能的话,调用元素的底部会与视口底部平齐),但是顶部不一定平齐。

专有扩展

文档模式

  • 页面的文档模式决定了可以使用什么功能

children属性

  • 是HTMLCollection的实例,只包含元素中同样还是元素的子节点

contains()

  • 调用这个方法的是祖先节点。这个方法接收一个参数,即要检测的后代节点。如果是后代节点则返回true。
  • DOM Level 3 compareDocumentPosition()也能确定节点间的关系。返回一个表示该关系的位掩码。
掩码 节点关系
1 无关
2 居前
4 局后
8 包含(给定的节点是参考节点的祖先)
16 被包含(给定节点是参考节点的后代)

插入文本

  • innerText属性会过滤html标签
  • outerText属性将作用范围扩大到了包含调用它的节点之外。
  • scrollIntoViewIfNeeded(alignCenter) 如果不可见才滚动,参数设置true表示尽量将元素显示在视口中部
  • scrollByLines() 将元素的内容滚动指定行高
  • scrollByPages() 将元素的内容滚动指定页面高度

JavaScript高级程序设计读书笔记10

发表于 2018-01-24 | 分类于 读书笔记 , JavaScript高级程序设计

第10章 DOM

节点层次

  • DOM可以将任何HTML或XML文档描绘成一个由多层节点构成的结构
  • 文档节点是每个文档的根节点

Node类型

  • DOM1级定义了一个Node接口,所有节点类型都继承自Node类型,因此所有节点类型都共享着相同的基本属性和方法
  • nodeType属性:1 元素节点,2 特性节点,3文档类型节点
  • nodeName属性:指定节点的节点名称
  • nodeValue属性:设置或返回指定节点的节点值
  • previousSilbing属性:返回同一树层级中指定节点的前一个节点
  • nextSibling属性:返回指定节点之后紧跟的节点
  • parentNide属性:指向文档树中的父节点
  • childNodes属性:保存着一个NodeList对象。可以通过方括号来访问NodeList的值,且有length属性,但它并不是Array的实例。
  • arguments对象使用Array.prototype.slice()方法可以将其转换成数组
  • 同样,NodeList也可以转换为数组var arrayOfNodes=Array.prototype.slice.call(someNode.childNodes,0),但此代码在IE8以前的版本无效

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function convertArray(nodes){
    var array=null;
    try{
    array=Array.prototype.slice.call(nodes,0);//IE以后及其他浏览器
    }catch(ex){
    array=new Array();
    for(var i=0,len=nodes.length;i<len;i++){//IE8及以前的版本
    array.push(nodes[i]);
    }
    }
    return array;
    }
  • ownerDocument:指向表示整个文档的文档节点

    操作节点

  • 任何DOM节点都不能同时出现在文档中国的多个位置上,如果调用appendChild()时传入了父节点的一个子节点那么该节点就会成为父节点的最后一个子节点
  • insertBefore():放在某个特定的节点之前
  • replaceChild():替换节点。被替换的节点仍在还在文档中,但它在文档中没有了自己的位置
  • removeChild():移除节点。被移除的节点仍在还在文档中,但它在文档中没有了自己的位置
  • cloneNode(true):深复制,复制节点机器整个子节点数
  • cloneNode(false):浅复制,只复制节点本身
  • normalize():如果找到空文本节点,就删除,找到相邻的文本节点,就合并

    Document类型

  • Document类型可以表示HTML页面或者其他基于XML的文档
  • 最常见的应用是作为HTMLDocument实例的document对象

    文档的子节点

  • documentElement属性:始终指向HTML页面中的元素
  • childNodes:访问文档元素
  • body属性:直接指向元素
  • doctype属性:取得在<!DOCTYPE>中的DocumentType节点
  • 文档类型是只读的,而且只能有一个元素节点

    文档信息

  • document.title:元素的文本,显示在浏览器窗口的标题栏或标签页上
  • document.URL:页面完整的url
  • document.domain:页面的域名
  • document.referrer:链接到当前页面的那个页面的url。在没有来源页面的情况下,可能包含空字符串。
  • 只有domain可以设置,但也并不是任何值。
  • 由于跨域安全限制,来自不同子域的页面无法通过JS通信,而将每个也没按的document.domain设置为相同的值,这些页面就可以互相访问对方包含的JS对象了。
  • 如果域名一开始是松散的,就不能再将其设置为紧绷的。

    查找元素

  • getElementById:严格匹配,包括大小写。只返回文档中第一次出现的元素
  • getElementsByTagName():返回一个NodeList.在HTML文档中,返回一个HTMLCollection对象.
  • HTMLCollection.namedItem():可以通过元素的name特性取得集合中的项,效果和方括号语法相同
  • getElementsByName():最常用的情况是取得单选按钮

    DOM一致性检测

  • document.implementation:检测浏览器实现了DOM的哪些部分
  • hasFeature():DOM1级只为document.implementation规定了一个方法

    文档写入

  • 如果在文档加载结束后再调用document.write(),输出的内容将会重写整个页面。
  • 以下代码页面上只有Tue Jan 23 2018 18:08:35 GMT+0800 (中国标准时间)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <p>The time is:</p>
    <script type="text/javascript">
    window.onload=function(){
    document.write("<strong>"+(new Date()).toString()+"</strong>");
    };
    </script>

    </body>
    </html>

Element类型

  • tagName属性:和nodeName一样
  • 在HTML中标签名始终都会以全部大写表示,在XML中标签名会始终与源代码中的保持一致if(element.tagName.toLowerCase()=="div"){}

    HTML元素

  • 所有HTML元素都由HTMLElement类型表示,不是直接通过这个类型,也是通过它的子类型来表示
  • id、title、lang、dir、className

    取得特性

  • getAttribute()
  • 通过属性的值和通过getAttribute都可以得到特性值,但是自定义的特性一定要用getAttribute
  • 注意,className的特殊性<div class="bd"></div> div.className div.getAttribute("class")
  • style和onclick这样的时间处理程序在通过属性的值和通过getAttribute两种方法返回的值不同

    设置特性

  • setAttribute():设置属性,也可以直接给属性赋值来设置
  • removeAttribute():清除特性的值,从元素中完全删除特性

    attributes属性

  • ELement类型是使用attributes属性的唯一一个DOM节点类型。
  • attributes属性中包含一个NamedNodeMap,与NodeList类似。

    创建元素

  • createElement()
  • 在HTML文档椎间盘美好你起个名大小写,在XML(或者XHTML)文档中区分大小写

    元素的子节点

  • 如果需要通过childNodes属性遍历子节点,通常都要先吉安查一个nodeType属性

Text类型

  • 可以通过nodeValue或者data属性访问Text节点包含的文本
  • createTextNode():创建文本节点
  • normalize():如果在包含两个或多个本文节点的父元素上调用该方法,则会将所有文本节点合并成一个节点
  • splitText():按照指定的位置分割nodeValue值

DOM操作技术

动态脚本

  • 在页面加载时不存在,但将来的某一时刻通过修改DOM动态添加的脚本
  • 创建动态脚本有两种方式:插入外部文件和直接插入JS代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //插入外部文件
    function loadScript(url){
    var script=document.createElement("script");
    script.type="text/javascript";
    script.src=url;
    document.body.appendChild(script);
    }
    loadScript("client.js");
    //直接插入JS代码
    function loadScriptString(code){
    var script=document.createElement("script");
    script.type="text/javascript";
    try{
    script.appendChild(document.createTextNode(code);
    }catch(ex){
    script.text=code; //用于IE,IE将<script>视为一个特殊的元素,不允许DOM访问其子节点
    }
    document.body.appendChild(script);
    }

动态样式

  • 在页面加载完成后添加到页面中的。
  • 加载外部样式文件的过程是异步的,也就是加载样式与执行JS代码的过程没有固定的次序。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //插入外部文件
    function loadStyles(url){
    var link=document.createElement("link");
    link.rel="stylesheet";
    link.type="text/css";
    link.href=url;
    var head=document.getElementsByTagName("head")[0];
    head.appendChild(link);
    }
    loadStyles("styles.css");
    //直接插入JS代码
    function loadStyleString(css){
    var style=document.createElement("style");
    style.type="text/css";
    try{
    style.appendChild(document.createTextNode(css));
    }catch(ex){
    style.styleSheet.cssText=css; //用于IE,IE将<style>视为一个特殊的元素,不允许DOM访问其子节点
    }
    var head=document.getElementsByTagName("head")[0];
    head.appendChild(style);
    }
    loadStyleString("body{background-color:red}");

操作表格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var table=document.createElement("table");
table.border=1;
table.width="100%";
var tbody=document.createElement("tbody");
table.appendChild(tbody);
//创建第一行
tbody.insertRow(0);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode("cell 1,1"));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[0].appendChild(document.createTextNode("cell 2,1"));
//创建第二行
tbody.insertRow(1);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode("cell 1,2"));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[0].appendChild(document.createTextNode("cell 2,2"));

使用NodeList

  • NodeList、NamedNodeMap、HTMLCollection这三个集合是动态的,每当文档结构发生变化时,它们都会得到更新。
  • 因此,它们始终都会保存着最新、最准确地信息。
  • 从本质上说。所有NodeList对象都是在访问DOM文档时实时运行的查询。
  • 浏览器不会将创建的所有集合都保存在一个列表中,而是在下一次访问集合时再更新集合。

    1
    2
    3
    4
    5
    6
    7
    8
    var divs=document.getElementsByTagName("div"),
    i,
    len,
    div;
    for(i=0,len=divs.length;i<len;i++){
    div=document.createElement("div");
    document.body.appendChild(div);
    }
  • length属性初始化len变量,然后将迭代器与该变量进行比较,避免直接比较造成的无限循环问题。

JavaScript高级程序设计读书笔记9

发表于 2018-01-24 | 分类于 读书笔记 , JavaScript高级程序设计

第9章 客户端检测

  • 不到万不得已,不要使用客户端检测

    能力检测

  • 最常用也最为人们广泛接受的客户端检测形式是能力检测(又称特性检测)
  • 1.先检测答到目的的最常用的特性,保证代码最优化,在多数情况下都可以避免测试多个条件
  • 2.必须测试实际要用到的特性,即一个特性国的存在,不一定意味着另一个特性也存在

    更可靠的能力检测

  • 确定一个对象是否支持排序,检测其是否是有排序属性并不能确定这个对象是否支持排序,更好的方法是确定sort是不是一个函数

    1
    2
    3
    function isSortable(object){
    return typeof object.sort=="function";
    }
  • 但是宿主兑现更没有义务让typeof返回合理的值,因此在浏览器环境下的测试任何对象的某个特性是否存在,要使用下面这个函数

    1
    2
    3
    4
    function isHostMethod(object,property){
    var t=typeof object[property];
    return t=='function'||(!!(t=='object'&&object[property]))||t=='unknown';
    }

能力检测,不是浏览器检测

  • 检测某个或几个特性并不能够确定浏览器
  • 确定浏览器是否支持Netscape风格的插件
    var hasNSPlugins=!!(navigator.plugins&&navigator.plugins.length);
  • 确定浏览器是否支持DOM1级规定的能力
    var hasDOM1=!!(document.getElementById&&document.createElement&&document.getElementsByTagName);

    怪癖检测

  • 怪癖检测的目的是识别浏览器的特殊行为
  • IE8及更早的版本会存在:如果某个实例属性与[[Enumerable]]标记为false的某个原型属性同名,那么该实例属性将不会出现在for-in循环中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var hasDontEnumQuirk = function () {
    var o = { toString: function () {} };
    for (var prop in o) {
    if (prop == "toString") {
    return false;
    }
    }
    return true;
    }();
    console.log(hasDontEnumQuirk);
  • Safari3以前的版本会枚举被隐藏的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    var hasEnumShadowsQuirk = function () {
    var o = {toString: function () {}};
    var count = 0;
    for (var prop in o) {
    if (prop == "toString") {
    count++;
    }
    }
    return (count==1);
    }();
    console.log(hasEnumShadowsQuirk);

用户代理检测

  • 用户代理检测通过检测用户代理字符串来确定实际使用的浏览器
  • 在每一次HTTP请求过程中,用户代理字符串是作为响应首部发送的,而且该字符串可以通过JavaScript的navigator.userAgent属性访问
  • 在服务器端,通过检测用户代理字符串来确定用户使用过的浏览器是一种常用且广为接受的做法
  • 在客户端,检测用户代理是一种万不得已采用的做法
  • 电子欺骗:浏览器通过在自己的用户代理字符串中加入一些错误或误导的信息,达到欺骗服务器的目的

    用户代理字符串的历史

  • 加密类型:即安全加密的类型。U:128位加密 I:40位加密 N:未加密

    用户代理字符串检测技术

  • 一般情况下,知道呈现引擎和最低限度的版本就足以确定正确的操作方法了

    识别呈现引擎

  • 五大呈现引擎:IE、Gecko、WebKit、KHTML、Opera
  • 正确地检测顺序:1.Opera 2.WebKit 3.KHTML 4.Gecko 5.IE

    识别浏览器

  • 只有呈现引擎还不能说明存在所需的JS功能

    识别平台

  • 在某些条件下,平台可能是必须关注的问题
  • 目前三大主流平台:Windows、Mac、Unix

    使用用户代理检测的场景

  1. 不能直接准确地使用能力检测或怪癖检测
  2. 用一款浏览器在不同平台下具备不同的能力
  3. 为了跟踪分析等目的需要知道确切的浏览器

JavaScript高级程序设计读书笔记8

发表于 2018-01-22 | 分类于 读书笔记 , JavaScript高级程序设计

第8章 BOM

window对象

  • window对象既是JS访问浏览器窗口的一个接口,又是ECMAScript规定的Global对象

    全局作用域

  • 所有在全局作用域中声明的变量、函数都会成为window对象的属性和方法
  • 全局变量不能通过delete操作符删除,直接在window对象上的定义的属性可以。
  • 尝试访问未声明的变量会抛出错误,但是通过查询window对象,可以知道某个可能未声明的变量是否存在。
  • var newValue=window.oldVlueoldValue为定义,newValue的值是undefined

    窗口关系及框架

  • 如果页面中包含框架,则每个框架都拥有自己的window对象,并且保存在frames集合中。
  • top对象始终指向最高(最外)层的框架,也就是浏览器窗口
  • parent对象始终指向当前框架的直接上层框架
  • self对象始终指向window
  • 在使用框架的情况下, 浏览器会存在多个Global对象。在每个框架中定义的全局变量会自动成为框架中window对象的属性。
  • 由于每个window对象都包含原生类型的构造函数,因此每个框架都有一套自己的构造函数,这些构造函数一一对应,但并不相等。

    窗口位置

  • screenLeft(screenX):表示窗口相对于屏幕左边的位置
  • screenTop(screenY):表示窗口相对于屏幕上边的位置
  • 通过以下代码可以跨浏览器取得窗口左边和上边的位置

    1
    2
    var leftPos=(typeof window.screenLeft=="number")?window.screenLeft:window.screenX;
    var topPos=(typeof window.screenTop=="number")?window.screenTop:window.screenY;
  • 无法在跨浏览器的条件下取得窗口左边和上边的精确坐标值

  • moveTo()和moveBy()方法可以跨浏览器地将窗口精确地移动到一个新位置
  • moveTo():接收新位置的x和y坐标值
  • moveBy():在水平和垂直方向上移动的像素数
  • moveTo()和moveBy()方法可能被浏览器禁用,且不适用于框架,只能对最外层window对象使用

窗口大小

  • 无法确定浏览器窗口本身的大小,但是可以取得页面视口的大小

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var pageWidth=window.innerWidth;
    var pageHeight=window.innerHeight;
    if(typeof pageWidth!="number"){
    if(document.compatMode=="CSS1Compat"){
    pageWidth=document.documentElement.clientWidth;
    pageHeight=document.documentElement.clientHeight;
    }else{
    pageWidth=document.body.clientWidth;
    pageHeight=document.body.clientHeight;
    }
    }
  • resizeTo()和resizeBy()方法可以调整浏览器窗口的大小

  • resizeTo():接收新窗口的宽度和高度
  • resizeBy():接收新窗口和原窗口的宽度与高度之差
  • resizeTo()和resizeBy()方法可能被浏览器禁用,且不适用于框架,只能对最外层window对象使用

    导航和打开窗口

  • winodw.open():导航一个特定的url,也可以打开一个新的浏览器窗口。
  • window.open()会返回一个指向新窗口的引用,可以像操作其他窗口一样操作新打开的窗口
  • close()方法仅适用于通过winodw.open()打开的弹出窗口,对于浏览器的主窗口,没有得到用户的允许不能关闭。
  • 新创建的window对象有一个opener属性,保存着打开它的原始窗口对象。
  • 在新创建的标签页opener设置为null,表示在单独的进程中运行新标签页,不需要与打开它的标签页通信。
  • 在弹出窗口被屏蔽时,就应该考虑两种可能性。

    1.如果浏览器内置的屏蔽程序阻止的弹出窗口,window.open()会返回null值

1
2
3
4
var wroxWin=window.open("http://www.wrox.com","_blank");
if(wroxWin==null){
alert("The popup was blocked!")
}

2.如果是浏览器扩展或其他程序阻止的弹出窗口,那么window.open通常会抛出一个错误。

1
2
3
4
5
6
7
8
9
10
11
12
var blocked=false;
try {
var wroxWin = window.open("http://www.wrox.com", "_blank");
if (wroxWin == null) {
blocked = true;
}
}catch(ex){
blocked=true;
}
if(blocked){
alert("The popup was blocked!");
}

间歇调用与超时调用

  • setTimeout():超时调用,在指定的时间过后执行代码。返回一个代表计划执行代码的唯一标识符ID,可以用clearTimeout(ID)来取消超时调用计划。
  • 超时调用的代码都是在全局作用域中执行的,函数中的this值在非严格模式下指向window对象,在严格模式下是undefiend。
  • setInterval():间歇调用,按照指定的时间间隔重复执行代码,直至间歇调用被取消或者页面被卸载。返回一个代表计划执行代码的唯一标识符ID,可以用clearTimeout(ID)来取消间歇调用计划。
  • 一般使用超时调用来模拟间歇调用,因为间歇调用可能会在前一个间歇调用结束之前启动,最好不要使用间歇调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    var num=0;
    var max=10;
    function incrementNumber(){
    num++;
    if(num<max){
    setTimeout(incrementNumber(),500);
    }else{
    alert("Done");
    }
    }
    setTimeout(incrementNumber(),500);

系统对话框

  • alert()、confirm()、prompt()都是同步的
  • alert():只有确认按钮
  • confirm():确认和取消按钮

    1
    2
    3
    4
    5
    if(confirm("1")){
    alert("yes");
    }else{
    alert("no");
    }
  • prompt():确认和取消按钮,还有一个文本框

    1
    2
    3
    4
    var result=prompt("what is your name","xyy");
    if(result!==null){
    alert("Welcome,"+result);
    }
  • window.print() window.find()异步显示,打印和查找

    location对象

  • location包含有关当前 URL 的信息。
  • window.location()和document.location()引用的是同一个对象

    查询字符串参数

  • 解析字符串,逐个访问其中的每个查询字符串参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function getQueryStringArgs() {
    //取得查询字符串并去掉开头的问号
    var qs = (location.search.length > 0 ? location.search.substring(1) : ""),
    //保存数据的对象
    args = {},
    //取得每一项
    items = qs.length ? qs.split("&") : [],
    item = null,
    name = null,
    value = null,
    //在for循环中使用
    i = 0,
    len = items.length;
    //逐个将每一个项添加到args对象中
    for (i = 0; i < len; i++) {
    item=item[i].split("=");
    //用decodeURIComponent分别解码name和value,因为查询字符串应该是被编码过的
    name=decodeURIComponent(item[0]);
    value=decodeURIComponent(item[1]);
    if(name.length){
    args[name]=value;
    }
    }
    return args;

    }

位置操作

  • location.assign(URL)可加载一个新的文档
  • location.replace(newURL)用一个新文档取代当前文档,不能用后退回到前一个页面
  • location.reload(force)重新加载当前文档
  • location.reload()有可能从缓存中加载
  • location.reload(true)强制从服务器加载

    navigator对象

  • navigator包含有关浏览器的信息

    检测插件

  • 普通浏览器和IE中检测插件的方法不一样,所以一般针对每个插件分别创建检测函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function hasPlugin(name){
    name=name.toLowerCase();
    for(var i=0;i<navigator.plugins.length;i++) {
    if (navigator.plugins[i].name.toLowerCase().indexOf(name) > -1) {
    return true;
    }
    }
    return false;
    }
    //hasIEPlugin中的name是只接收一个COM标识符作为参数
    function hasIEPlugin(name){
    try{
    new ActiveXObject(name);
    return true;
    }catch(ex){
    return false;
    }
    }
    function hasFlash(){
    var result=hasPlugin("Flash");
    if(!result){
    result=hasIEPlugin("ShockwaveFlash.ShockwaveFlash");
    }
    return result;
    }
    alert(hasFlash());

注册处理程序

  • Navigator.registerContentHandler()和registerProtocolHandler()可以让一个站点指明它可以处理特定类型的信息

screen对象

  • screen保存着与客户端显示器有关的信息,其中包括浏览器窗口外部的显示器的信息,如像素宽度和高度等

history对象

  • history对象保存着用户上网的历史记录,从窗口被打开的那一刻算起。
  • history.go() hitory.back() history.foeword()实现在历史记录中向后或向前导航到任意页面
  • history.length()保存历史记录的数量,对于加载到窗口、标签页或框架中国的第一个页面而言,history.length=0

JavaScript高级程序设计读书笔记7

发表于 2018-01-21 | 分类于 读书笔记 , JavaScript高级程序设计

第7章 函数表达式

  • 定义函数的方式有两种:一种是函数声明,另一种是函数表达式
  • 函数声明的一个重要特征是函数声明提升,在执行代码之前会先读取函数声明,可以把函数声明放在调用它的语句后面
  • var functionName=function(arg0,arg1,arg2){ //函数体 };函数表达式情况下创建的函数叫做匿名函数,因为function关键字后面没有标识符。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //以下做法错误,因为函数声明提升
    if(condition){
    function sayHi(){
    alert("Hi");
    };
    }else{
    function sayYo(){
    alert("Yo");
    };
    }
    //以下做法正确
    var sayHi;

    if(condition){
    sayHi=function(){
    alert("Hi");
    };
    }else{
    sayHi=function(){
    alert("Yo");
    };
    }

递归

  • 在编写递归函数时,使用arguments.callee总比使用函数名更加保险
  • 但在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误。可以通过使用命名函数表达式来达成相同的结果
    1
    2
    3
    4
    5
    6
    7
    var factorial=(function f(num){
    if(num<=1){
    return 1;
    }else{
    return num*f(num-1);
    }
    });

闭包

  • 闭包是指有权访问另一个函数作用域中的变量的函数
  • 创建闭包的常见方式,就是在一个函数内部创建另一个函数
  • 总的来说,当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。但作用域链中,外部函数的活动对象始终位于第二位,外部函数的外部函数的活动对象处于第三位……直至作用域链重点的全局执行环境。
  • 当创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中
  • 当调用函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象被创建并被推入执行环境作用域链的前端。
  • 一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。但是闭包情况不同
  • 在另一个函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function createCpmparisonFunction(propertyName){
    return function(object1,object2){
    var value1=object1[propertyName];
    var value2=object2[propertyName];
    if(value1<value2){
    return -1;
    }else if(value1>value2){
    return 1;
    }else{
    return 0;
    }
    }
    }
    //创建比较函数
    var compareNames=createCpmparisonFunction("name");
    //调用函数
    var result=compareNames({name:"Nicholas"},{name:"Greg"});
    //解除对匿名函数的引用,以便释放内存
    compareNames=null;
  • 当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。

  • 由于闭包会携带包含它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

    闭包与变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function createFunctions(){
    var result = new Array();
    for (var i = 0; i < 10; i++){
    result[i] = function(){
    return i;
    };
    }
    return result;
    }
  • 以上代码每个函数都返回10

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function createFunctions(){
    var result = new Array();
    for (var i = 0; i < 10; i++){
    result[i] = function(num){
    return function(){
    return num;
    };
    }(i);
    }
    return result;
    }
  • 以上代码函数返回0,1,2……因为参数是按值传递的,所以会将变量i的当前值复制给参数num

  • function(){}()这样写的作用:1.使这段代码被载入时候自动执行。2.避免污染全局变量。

关于this对象

1
2
3
4
5
6
7
8
9
10
11
var name="The Window";
var object={
name:"My Object",
getNameFunc:function(){
return function(){
return this.name;
};
}
};

alert(object.getNameFunc()()); //The Window
  • 每个函数被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象位置,因为永远不可能直接访问外部函数中的这两个变量。
  • 解决方法:把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var name="The Window";
    var object={
    name:"My Object",
    getNameFunc:function(){
    var that=this;
    return function(){
    return that.name;
    };
    }
    };

    alert(object.getNameFunc()()); //My Object

内存泄漏

  • 内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
  • 如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function assignHandler(){
    var element=document.getElementById("someElement");
    element.onclick=function(){
    alert(element.id);
    };
    }
    //改进后
    function assignHandler(){
    var element=document.getElementById("someElement");
    var id=element.id;
    element.onclick=function(){
    alert(id);
    };
    element=null;
    }
  • 即使闭包不直接饮用element,包含函数的的活动对象中也依然会保存一个引用,不能解决内存泄漏问题。

  • 所以一定要把element设置为null,这样就可以解除对DOM对象的引用,顺利减少其引用数,确保正常回收占用的内存

    模仿块级作用域

  • 面对的问题:JS中没有块级作用域,即使重新声明同一个变量,只会对后续声明视而不见
  • 解决方法:块级作用域(私有作用域)

    1
    2
    3
    (function(){
    //这里是块级作用域
    })();
  • 定义并立即调用了一个匿名函数,将函数声明包含在一堆圆括号中,实际上是一个函数表达式,其后的另一对圆括号会立即调用这个函数。

    1
    2
    3
    function(){
    //这里是块级作用域
    }(); //错误!
  • 函数声明后不能跟圆括号,但是函数表达式后面可以跟圆括号。将函数声明转换成函数表达式的方法是加上一对圆括号。

  • 在匿名函数中定义的任何变量,都会在执行结束时被销毁。
  • 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。
  • 可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数还行完毕,就可以立即销毁其作用域链了。

    私有变量

  • 任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。
  • 特权方法:有权访问私有变量和私有函数的公有方法。
  • 有两种在对象上创建特权方法的方式:

    1.在构造函数中定义特权方法,在实例化后,除了使用特权方法没有任何方法可以访问私有变量和方法
    存在的问题:构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量实现特权方法可以避免这个问题。

1
2
3
4
5
6
7
8
9
10
function MyObject(){
var privateVariable=10;
function privateFunction(){
return false;
}
this.publicMethod=function(){
privateVariable++;
return privateFunction();
}
}

2.静态私有变量,所有实例共享私有变量和函数,在一个实例上改变变量会影响所有变量
存在问题:因为使用原型而增进代码的复用,每个实例都没有自己的私有变量。

1
2
3
4
5
6
7
8
9
10
11
12
(function(){
var privateVariable=10;
function privateFunction(){
return false;
}
MyObject=function(){
};
MyObject.prototype.publicMethod=function(){
privateVariable++;
return privateFunction();
};
})();

模块模式

  • 模块模式:为单利创建私有变量和特权方法
  • 使用场景:必须创建一个对象,并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,就可以使用模块模式。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var singleton=function(){
    var privateVariable=10;
    function privateFunction(){
    return false;
    }
    return {
    publicProperty:true,
    publicMethod:function(){
    privateVariable++;
    return privateFunction();
    }
    };
    }();

增强的模块模式

  • 使用场景:适合那些单例必须是某种类型的实例,同时还必须添加某些属性或方法对其记忆增强的情况。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var singleton = function () {
    var privateVariable = 10;

    function privateFunction() {
    return false;
    }

    var object = new CustomType();

    object.publicProperty = true;
    object.publicMethod = function () {
    privateVariable++;
    return privateFunction();
    };
    return object;
    }();

JavaScript高级程序设计读书笔记6

发表于 2018-01-18 | 分类于 读书笔记 , JavaScript高级程序设计

第6章 面向对象的程序设计

理解对象

属性类型

  • ECMAScript中有两种属性:数据属性和访问器属性

    数据属性

  • 数据属性包含一个数据值的位置
  • 数据属性有4个描述其行为的特性:
  • Configurable:能否通过delete删除属性从而重新定义属性,能否删除属性的特征,或者能否把属性修改为访问器属性。
  • Enumerable:表示能否通过for-in循环返回属性
  • Writable:表示能否修改属性的值
  • Value:包含这个属性的数据值,默认值undefined
  • 直接在对象上定义属性,则Configurable、Enumerable、Writable特性默认值是true,在调用Object.defineProperty()方法创建一个新属性时,如果不指定,则以上三个特性都是false。

    1
    2
    3
    4
    5
    6
    7
    8
    var person={};
    Object.defineProperty(person,"name",{
    writable:false,
    value:"Nicholas"
    });
    alert(person.name); //"Nicholas"
    person.name="Greg";
    alert(person.name); //"Nicholas"
  • 在把configurable设置为false后,就不能再把它变回可配置了,调用Object.defineProperty()方法修改除writable之外的特性就会导致错误

访问器属性

  • Configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。
  • Enumerable:能否通过for-in循环返回属性
  • get:在读取属性时调用的函数,
  • 值undefined
  • set:在写入属性时调用的函数,默认值undefined
  • 访问器属性不能直接定义,必须使用Object.defineProperty()来定义。
  • 属性前面的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性

    定义多个属性

  • Object.defineProperties():一次定义多个属性

    读取属性的特性

  • Object.getOwnPropertyDescriptor():取得给定属性的描述符,返回一个对象

创建对象

工厂模式

  • 解决列创建多个相似对象的问题,但是没有解决对象识别的问题(即怎样知道一个对象的类型)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function createPerson(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName=function(){
    alert(this.name);
    };
    return o;
    }
    var person1=createPerson("Nicholas",29,"software Engineer");

构造函数模式

1
2
3
4
5
6
7
8
9
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
}
}
var person1=new Person("Nicholas",29,"software Engineer");
  • 工厂模式和构造函数模式的区别:

    1. 没有显式地创建对象
    2. 直接将属性和方法赋给this对象
    3. 没有return语句
  • 构造函数使用的都是大写字母开头,非构造函数则是小写字母开头

  • new操作符调用构造函数实际上会经历以下4个步骤:

    1. 创建一个新对象
    2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
    3. 执行构造函数中的代码(为这个新对象添加属性)
    4. 返回新对象
  • person1对象有一个constructor(构造函数)属性,该属性指向Person。

    1
    2
    alert(person1.constructor==Person);  //true
    alert(person1.constructor==Object); //false
  • person1对象是Person的实例

    1
    2
    alert(person1 instanceof Person);  //true
    alert(person1 instanceof Object); //true

将构造函数当做函数

  • 构造函数与其他函数的唯一区别:调用它们的方式不同
  • 任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那他跟普通函数没有区别
1
2
3
4
5
6
7
8
9
10
11
12
//当做构造函数使用
var person=new Person("Nicholas",29,"software Engineer");
person.sayName();

//作为普通函数调用
Person("Greg",27,"Doctor"); //添加到window
window.sayName();//当在全局作用域中调用一个函数时,this对象总是指向Global对象()(在浏览器中就是window对象)

//在另一个对象的作用域中调用
var o=new Object();
Person.call(o,"Kristen",29,"Nurse"); //在对象o的作用域中调用
o.sayName();

构造函数的问题

  • 构造函数的主要问题:每个定义的方法都要在每个实例上重新创建一遍
  • 在每个实例上创建函数对象没有必要,可以通过把函数定义转移到构造函数外部来解决这个问题
  • 可是又有新的问题
    1. 在全局作用域中定义的函数实际上只能被某个对象调用,折让全局作用域名不副实
    2. 如果对象需要定义很多方法,那么久要定义很多的全局函数,丝毫没有封装性可言

原型模式

  • 每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。用途:包含可以有特定类型的所有实例共享的属性和方法
  • 原型模式与构造函数模式的区别:新对象的这些属性和方法是由所有实例共享的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function Person(){
    }

    Person.prototype.name="Nicholas";
    Person.prototype.age=29;
    Person.prototype.job="Software Engineer";
    Person.prototype.sayName=function(){
    alert(this.name);
    };
    var person1=new Person();
    person1.sayName(); //"Nicholas"

    var person2=new Person();
    person2.sayName(); //"Nicholas"
    alert(person1.sayName==person2.sayName); //true

理解原型对象

  • 只要创建一个新函数,就会根据特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。
  • 所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是指向一个prototype属性所在函数的指针。
  • 当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。这个链接存在于实例与构造函数的原型对象之间,实例与构造函数没有直接关系。

  • isPrototyprOf():确定对象之间是否存在在[[prototype]]这种关系

    1
    alert(Person.prototype.isPrototypeOf(person1));  //true
  • Object.getPrototypeOf():返回[[prototype]]的值

    1
    alert(Object.getPrototypeOf(person1).name);   //"Nicholas"
  • hasOwnProperty():可以检测一个属性是存在于实例中,还是存在于原型中。存在于实例中,则返回true。

    1
    2
    3
    4
    5
    6
    var person1=new Person();
    alert(person1.hasOwnProperty("name")); //false 来自原型
    person1.name="Greg";
    alert(person1.hasOwnProperty("name")); //true 来自实例
    delete person1.name;
    alert(person1.hasOwnProperty("name")); //false 来自原型
  • 多个对象实例共享原型所保存的属性和方法的基本原理:每当代码读取某个对象的某个属性时,都会执行一次搜索,首先从对象实例本身开始,如果在事例中找到了给定名字的属性,则返回该属性的值,如果没有找到,则继续搜索指针指向的源性对象,在源性对象中查找具有给定名字的属性。

  • 原型最初只包含constructor属性,而该属性也是共享的,因此可以通过对象实例访问。
  • 使用delete操作符可以删除实例属性

原型与in操作符

  • in操作符有两种使用方法:单独使用和在for-in循环中使用。
  • 在单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实力中还是原型中

    1
    2
    3
    function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name)&&(name in object);
    }
  • 在for-in循环中使用时,返回的是所有能够通过对象访问的、可枚举的属性,屏蔽了原型中不可枚举属性的实例属性也会在for-in循环中返回。

  • 所有开发人员定义的属性都是可枚举的。
  • Object.keys():取得对象上所有可枚举的实例属性。PS:只有实例对象上的,和for-in不同,for-in还会包含原型中的属性和方法。
  • Object.getOwnProertyNames():得到所有实例属性,无论是否可枚举。PS:同样,只有实例对象上的,和for-in不同,for-in还会包含原型中的属性和方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function Person(){
    }

    Person.prototype.name="Nicholas";
    Person.prototype.age=29;
    Person.prototype.job="Software Engineer";
    Person.prototype.sayName=function(){
    alert(this.name);
    };

    var keys=Object.keys(Person.prototype);
    alert(keys); //name,age,job,sayName

    var p1=new Person();
    p1.name="Rob";
    p1.age=31;
    var p1keys=Object.keys(p1);
    alert(p1keys); //name,age

    alert(Object.getOwnPropertyNames(Person.prototype));//constructor,name,age,job,sayName

    alert(Object.getOwnPropertyNames(p1));//name,age

更简单的原型语法

  • 更常用的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function Person(){
    }
    Person.prototype={
    name:"Nicholas",
    age:29,
    job:"Software Engineer",
    sayName:function() {
    alert(this.name);
    }
    };
  • 以上方法完全重写了默认的prototype对象,因此constructor属性指向了Object。可以设置为指向Person。
    constructor:Person

  • 但这种重设constructor会导致她的Enumerable特性被设置为true。原生的constructor属性是不可枚举的。因此可以添加以下代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function Person(){
    }
    Person.prototype={
    name:"Nicholas",
    age:29,
    job:"Software Engineer",
    sayName:function() {
    alert(this.name);
    }
    };
    Object.defineProperty(Person.prototype,"constructor",{
    enumerable:false,
    value:Person
    });

原型的动态性

  • 对源性对象所做的任何修改都能够立即从实例上反应出来,即使是先创建了实例后修改原型也是如此。
  • 先创建实例后,重写整个原型对象,就把原型修改为另外一个对象,切断了构造函数与最初原型之间的联系。而实例中的指针还是指向最初的原型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function Person(){
    }
    var friend=new Person();
    Person.prototype={
    name:"Nicholas",
    age:29,
    job:"Software Engineer",
    sayName:function() {
    alert(this.name);
    }
    };
    friend.sayName(); //报错
  • 先修改原型,再创建实例就不会报错了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function Person(){
    }
    Person.prototype={
    name:"Nicholas",
    age:29,
    job:"Software Engineer",
    sayName:function() {
    alert(this.name);
    }
    };
    var friend=new Person();
    friend.sayName(); //报错

原生对象的原型

  • 创建的自定义类型,原生的引用类型,都是采用原型模式创建的
  • 可以像修改自定义对象的原型那样修改原生对象的原型,因此可以随时添加方法。

    原型对象的问题

  1. 省略了为构造函数传递初始化参数这一环节,所有的实例在默认情况下都取得相同的属性值。
  2. 实例1改变原型中引用类型值的属性,则实例2中的属性也会跟着改变。

组合使用构造函数模式和原型模式

  • 创建自定义类型的最常见方式,就是组合使用构造函数模式和原型模式
  • 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
  • 每个实例都会有自己的一份实例属性的副本,但同时又共享方法的引用,最大限度地节省了内存。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=["Shelby","Count"];
    }

    Person.prototype={
    constructor:Person,
    sayName:function(){
    alert(this.name);
    }
    }
    var person1=new Person("Nicholas",29,"Software Engineer");
    var person2=new Person("Greg",27,"Doctor");

    person1.friends.push("Van");
    alert(person1.friends); //Shelby,Count,Van
    alert(person2.friends); //Shelby,Count
    alert(person1.friends===person2.friends); //false
    alert(person1.sayName===person2.sayName); //true

动态原型模式

  • 动态原型模式将所有信息都封装在构造函数中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    if(typeof this.sayName!="function"){
    Person.prototype.sayName=function(){
    alert(this.name);
    };
    }
    }
    var person1=new Person("Nicholas",29,"Software Engineer");
    person1.sayName();
  • 这里原型所做的修改,能够立刻在所有实例中得到反映。

  • if语句检查的可以是初始化之后应该存在的任何属性或方法,不必检查每一个,只要检查其中一个即可。
  • instanceof操作符来确定对象类型

    寄生构造函数模式

  • 创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回创建的对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function Person(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName=function(){
    alert(this.name);
    };
    return o;
    }
    var person1=new Person("Nicholas",29,"software Engineer");
  • 除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。

  • 返回的对象与构造函数或者与构造函数的原型属性之间没有关系
  • 不能依赖instanceof操作符来确定对象类型

    稳妥构造函数模式

  • 稳妥对象:没有公共属性,其方法也不引用this的对象
  • 稳妥对象最适合在一些安全的环境中(这些问环境禁止使用this和new)或在防止数据被其他应用程序(如Mashup程序)改动时使用。
  • 稳妥构造函数遵循与计生构造函数类似的模式,担有两点不同:

    1.新创建对象的实例方法不引用this
    2.不适用new操作符调用构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function Person(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName=function(){
    alert(name);
    };
    return o;
    }
    var person1=Person("Nicholas",29,"software Engineer");
    person1.sayName(); //"Nicholas"
    person1.name="Greg";
    person1.sayName(); //"Nicholas"就算改变person1的那么也不会改变
  • 返回的对象与构造函数或者与构造函数的原型属性之间没有关系

  • 不能依赖instanceof操作符来确定对象类型

继承

  • 许多OO语言都支持两种继承方式:

    接口继承:只继承方法签名
    实现继承:继承实际的方法

原型链

  • 基本概念:原型对象等于另一个类型的实例,原型对象将包含一个指向另一个原型的指针,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,如此层层递进,构成了实例与原型的链条。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function SuperType(){
    this.property=true;
    }
    SuperType.prototype.getSuperValue=function(){
    return this.property;
    };
    function SubType(){
    this.subproperty=false;
    }
    //重写原型对象,SuperType实例
    SubType.prototype=new SuperType();
    SubType.prototype.getSubValue=function(){
    return this.subproperty;
    }
    var instance=new SubType();
    alert(instance.getSuperValue());

  • instance.getSuperValue()会经历三个步骤搜索:

    1.搜索实例
    2.搜索SubType.prototype
    3.搜索SuperType.prototype

别忘记默认的原型

  • 所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype
  • 因此,所有的自定义类型都会继承toString()valueOf()等默认方法的根本原因

确定原型与实例的关系

  • 两种方法:

    1.instanceof操作符:测试实例与原型链中出现打过的构造函数,结果就会返回true
    2.isPrototypeOf():只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型

谨慎地定义方法

  • 给原型添加方法的代码一定要放在替换原型的语句之后,即必须在用SuperType的实例替换原型之后,再定义这两个方法。
  • 通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样做就会重写原型链

原型链的问题

  • 两个问题:

    1.包含引用类型之的原型属性会被所有实例共享
    2.在创建子类型的实例时,不能向超类型的构造函数中传递参数

借用构造函数

  • 又称为伪造对象或经典继承
  • 思路:子类型构造函数的内部调用超类型构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function SuperType(){
    this.colors=["red","blue","green"];
    }
    function SubType(){
    //继承了SuperType
    SuperType.call(this);
    }
    var instance1=new SubType();
    instance1.colors.push("black"); //red,blue,green,black
    alert(instance1.colors);
    var instance2=new SubType();
    alert(instance2.colors);//red,blue,green
  • 会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码

    传递参数

  • 可以在子类型构造函数中向超类型构造函数传递参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function SuperType(name){
    this.name=name;
    }
    function SubType(){
    //继承了SuperType,同时传递了参数
    SuperType.call(this,"Nicholas");
    //实例属性
    this.age=29;
    }
    var instance1=new SubType();
    alert(instance1.name); //Nicholas
    alert(instance1.age); //29

借用构造函数的问题

  • 方法都在构造函数中定义,因此无法复用函数
  • 超类型的原型定义方法,对子类型而言是不可见。

组合继承

  • 又称为经典继承,将原型链和借用构造函数的技术组合到一起。
  • 思路:使用原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承
  • 好处:既可以在原型上定义方法实现函数复用,又保证每个实例都有它自己的属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function SuperType(name){
    this.name=name;
    this.colors=["red","blue","green"];
    }
    SuperType.prototype.sayName=function(){
    alert(this.name);
    };
    function SubType(name,age){
    //继承属性
    SuperType.call(this,name);
    this.age=age;
    }
    //继承方法
    SubType.prototype=new SuperType();
    SubType.prototype.constructor=SubType;
    SubType.prototype.sayAge=function(){
    alert(this.age);
    };
    var instance1=new SubType("Nicholas",29);
    instance1.colors.push("black");//red,blue,green,black
    alert(instance1.colors);
    instance1.sayName(); //Nicholas
    instance1.sayAge(); //29

    var instance2=new SubType("Greg",27);
    alert(instance2.colors); //red,blue,green
    instance2.sayName(); //Greg
    instance2.sayAge(); //27

原型式继承

  • 借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型
  • 其本质是object()对传入其中的对象执行了一次浅复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function object(o){ //返回一个对象的实例
    function F(){}
    F.prototype=o;
    return new F();
    }
    var person={
    name:"Nicholas",
    friends:["Shelby","Court","Van"]
    };
    var anotherPerson=object(person);
    anotherPerson.name="Greg";
    anotherPerson.friends.push("Rob");
    var yetAnotherPerson=object(person);
    yetAnotherPerson.name="Linda";
    yetAnotherPerson.friends.push("Barbie");
    alert(person.friends); //Shelby,Court,Van,Rob,Barbie
  • 两个新对象都将person作为原型,相当于创建了person对象的两个副本。

  • 使用场景:在想让一个对象与另一个对象保持累心搞得情况使用
  • 缺陷:包含的引用类型值的属性始终都会共享相应的值

    寄生式继承

  • 思路:创建一个金用于封装集成过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是他做了所有工作一样返回对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function object(o){
    function F(){}
    F.prototype=o;
    return new F();
    }
    function createAnother(original){
    var clone=object(original); //返回original类型的实例
    clone.sayHi=function(){
    alert("Hi");
    }; //以某种方式增强这个对象
    return clone; //返回这个对象
    }
    var person={
    name:"Nicholas",
    friends:["Shelby","Court","Van"]
    };
    var anotherPerson=createAnother(person);
    anotherPerson.sayHi(); //Hi
  • 使用场景:主要考虑对象而不是自定义类型和构造函数的情况下使用

  • 缺陷:使用寄生式继承为对象添加函数,会由于不能做到函数复用而降低效率。这一点和构造函数模式类似

寄生组合式继承

  • 组合继承最大的问题:无论什么情况下,都会调用两次超类型构造函数,一次是在创建子类型原型的时候,子类型原型会得到超类型属性。另一次是在子类型构造函数内部,子类型实例上会得到超类型属性。
  • 解决方式:寄生组合式继承
  • 思路:不必为了指定子类型的原型而调用超类型的构造函数,所需要的无非是超类型原型的一个副本而已。使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    function object(o){ //返回一个对象的实例
    function F(){}
    F.prototype=o;
    return new F();
    }

    function inheritPrototype(subType,superType){
    var prototype=object(superType.prototype); //创建超类型原型的一个副本
    prototype.constructor=subType;//为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认constructor属性
    subType.prototype=prototype;//将副本赋值给子类型的原型
    }
    function SuperType(name){
    this.name=name;
    this.colors=["red","blue","green"];
    }
    SuperType.prototype.sayName=function(){
    alert(this.name);
    };
    function SubType(name,age){
    SuperType.call(this,name);
    this.age=age;
    }
    inheritPrototype(SubType,SuperType);

    /* 替代以下语句
    SubType.prototype=new SuperType();
    SubType.prototype.constructor=SubType;
    */

    SubType.prototype.sayAge=function(){
    alert(this.age);
    };
  • 优势:只调用一次超类型构造函数,避免在子类型原型上创建不必要的多余属性,同时原型链还能保持不变。集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效的方式。

JavaScript高级程序设计读书笔记5

发表于 2018-01-15 | 分类于 读书笔记 , JavaScript高级程序设计

第5章 引用类型

Object类型

  • 创建Object实例的方式有两种:
    1. new操作符后跟Object构造函数
    2. 对象字面量表示法
1
2
3
4
5
6
7
8
9
10
11
//第一种
var person=new Object();
person.name="Nick";
person.age=29;

//第二种
var person={
name:"Nick",
age:29
};
}
  • var person={}和new Object()相同
  • 访问对象属性有两种方式:

    1. 点表示法 person.name
    2. 方括号语法 person["name"]
  • 方括号语法可以通过变量来访问属性,而且可以访问带有空格的属性。除非必须使用变量来访问属性,否则我们建议使用点表示法

Array类型

  • 数组的每一项可以保存任何类型的数据
  • Array数组的创建方式有两种:

    new方法,也可省略new var colors=new Array(3)
    字面量表示法。var colors=["red","blue","green"];

  • 构造函数传递值的方式有两种:

    传递数值,创建给定项数的数组
    var colors=new Array(3); //创建一个包含3项的数组
    传递其他类型的参数,会创建包含这个值的数组
    var names=new Array("Grey"); //创建一个包含1项,字符串是“Grey”的数组

  • 数组的length不是只读的,可以设置该属性,从数组的末尾移除项或向数组中添加项。

    1
    2
    3
    4
    5
    var colors=["red","blue","green"];
    colors.length=2;
    alert(colors[2]); //undefined
    colors.length=3;
    alert(colors[3]); //undefined
  • 添加项的方式:colors[colors.length]="black";

    检测数组

  • instanceof操作符不能应对两个以上不同的全局执行环境
  • Array.isArray(value)用于最终确定这个值是不是数组,而不管它是在哪个全局执行环境中创建的。
    1
    2
    3
    var colors=["red","blue","green"];
    alert(colors instanceof Array); //true
    alert(Array.isArray(colors)); //true

转换方法

  • 所有对象都具有toString()、valueOf()、toLocaleString()方法
  • valueOf()返回数组本身,toString()方法返回由每个值的字符串形式拼接而成的一个逗号分隔符的字符串
1
2
3
4
var colors=["red","blue","green"];
alert(colors.toString()); //red,blue,green
alert(colors.valueOf()); //red,blue,green
alert(colors); //red,blue,green
  • toLocaleString()与前两个方法的不同是:调用每一项的toLocaleString()方法
  • join()用作分隔符的字符串,返回包含所有数组项的字符串
  • 如果数组中的某一项的值是null或undefined,在join()、toString()、valueOf()、toLocaleString()方法返回的结果中以空字符串表示

栈与队列

  • 数组可以表现得像栈一样。栈(后进先出) push() pop()
  • 数组可以表现得像队列一样。队列(先进先出)push() shift()
  • push()都返回修改后的数组长度
  • pop()方法返回移除的项
  • shift()方法移除数组中的第一项并返回该项
  • unshift():数组前端添加任意个项并返回新数组的长度
  • 使用unshift()和pop()可以从相反方向模拟队列。
    1
    2
    3
    4
    5
    6
    7
    8
    var colors=new Array();
    var count=colors.unshift("red","green");
    alert(count); //2
    count=colors.unshift("black");
    alert(count); //3
    var item=colors.pop();
    alert(item); //green 这是green不是red
    alert(colors.length); //2

重排序方法

  • reverse()翻转数组项的顺序
  • sort()调用每个数组项的toString()方法,按升序排列。即使数组中是数值,比较的也是字符串

    1
    2
    3
    var values=[0,1,5,10,15];
    values.sort();
    alert(values); //0,1,10,15,5
  • sort()方法可以接受一个比较函数作为参数,改变以上问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function compare(value1,value2){
    if(value1<value2){
    return -1; //降序可以把这个改成1
    }else if(value1>value2){
    return 1; //降序可以把这个改成-1
    }else{
    return 0;
    }
    }

    var values=[0,1,5,10,15];
    values.sort(compare);
    alert(values); //0,1,5,10,15
  • 如果是数值类型的对象或valueof()是数值类型的对象,可以直接相减即可

    1
    2
    3
    function compare(value1,value2){
    return value1-value2; //升序,降序改为 value2-value1
    }

操作方法

  • concat()复制当前数组,将参数添加到复制的数组的尾部。
  • slice()接受返回项的起始和结束位置的参数,返回数组中不包括结束位置的项。传入的参数如果是负数,就用数组长度加上该数。slice()方法不会影响原始数组。
  • splice()的主要用途是向数组中插入项,但也有删除、插入、替换三种操作。splice(起始位置、要删除的项数、要插入的任意数量的项)

位置方法

  • indexOf()从头开始查找项,返回要查找项在数组中的位置。
  • lastindexOf()从末尾开始查找项,返回要查找项在数组中的位置。
  • 两个方法的查找都是严格相等的,和===一样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var numbers=[1,2,3,4,5,4,3,2,1];
    alert(numbers.indexOf(4)); //3
    alert(numbers.lastIndexOf(4)); //5

    alert(numbers.indexOf(4,4)); //5
    alert(numbers.lastIndexOf(4,4)); //3

    var person={name:"Nicholas"}; //person对象,有一个name属性
    var people=[{name:"Nicholas"}]; //people数组,含有一个{name:"Nocholas"}的对象

    var morePeople=[person]; //morePeople数组,含有一个person对象

    alert(people.indexOf(person)); //-1
    alert(morePeople.indexOf(person)); //0
  • 注意:people数组中的对象不是person,所以查不到。

  • 用==和===比较两个对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var a=new Object({name:"a"});
    var b=new Object({name:"a"}); //a和b指向两个对象
    alert(a===b); //false
    alert(a==b); //false

    var a=new Object({name:"a"}); //a和b指向同一个对象
    var b=a
    alert(a===b); //true
    alert(a==b); //true
  • 无论是用==还是===比较两个地址不同的对象,都是false。如果两个值指向同一个对象,则都是true

迭代方法

  • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true
  • filter():对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组
  • forEach():对数组中的每一项运行给定函数,没有返回值。本质上与使用for循环迭代数组一样
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
  • some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true

归并方法

  • reduce():迭代数组的所有项,向后遍历
  • reduceRight():向前遍历数组的所有项

Date类型

  • Date.parse()接收一个表示日期的字符串,返回相应日期的毫秒数
  • Date.UTC()同上,但是月份基于0开始

继承的方法

  • toLocalString()、toString()按照与浏览器设置的地区相适应的格式返回日期和时间
  • valueOf()不返回字符串

RegExp类型

  • 每个正则表达式都可带有一或多个标志,用以表明正则表达式行为

    g:全局模式
    i:不区分大小写模式
    m:表示多行模式

  • 正则表达式字面量始终会共享同一个RegExp实例,而使用构造函数创建的每一个新RegExp实例都是一个新实例

RegExp实例方法

  • 主要方法是exec():专门为捕获组而设计,返回包含第一个匹配项信息的数组。有两个属性:index表示匹配项在字符串中的位置,input表示应用正则表达式的字符串
  • test()方法,在模式与该参数匹配的情况下返回true,否则返回false

Function类型

  • 每个函数都是Function类型的实例,而且都与其他引用类型一样具有方法和属性。
  • 函数是对象,函数名是指针。因此,没有重载。
  • 使用不带圆括号的函数名是访问函数指针,而非调用函数
  • 三种定义函数的方式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //函数声明
    function sum(num1,num2){
    return num1+num2;
    }
    //函数表达式,注意最后一个分号
    var sum=function(num1,num2){
    return num1+num2;
    };
    //Function构造函数,不建议这样写
    var sum =new Function("num1","num2","return num1+num2");

函数声明与函数表达式

  • 解析器会率先读取函数声明,并使其在执行任何代码之前可用。因此如果使用函数表达式的方法定义函数,将函数表达式写在函数定义前面会报错

作为值的函数

  • 函数可以作为另一个函数的参数

    1
    2
    3
    function callSomeFunction(someFunction,someArgument){
    return someFunction(someArgument);
    }
  • 函数可以作为另一个函数的返回结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function createComparisonFunction(propertyName){
    return function(object1,object2){
    var value1=object1[propertyName];
    var value2=object2[propertyName];
    if(value1<value2){
    return -1;
    }else if(value1>value2){
    return 1;
    }else{
    return 0;
    }
    };
    } //升序排序

    var data=[{name:"Zachary",age:28},{name:"Nicholas",age:29}];

    data.sort(createComparisonFunction("name")); //按照name属性排序
    alert(data[0].name); //Nicholas

    data.sort(createComparisonFunction("age")); //按照age属性排序
    alert(data[0].name); //Zachary

函数内部属性

  • 函数内部:arguments对象,callee属性,指向拥有这个arguments对象的函数。经典的阶乘函数

    1
    2
    3
    4
    5
    6
    7
    function factorial(num){
    if(num<=1){
    return 1;
    }else{
    return num*arguments.callee(num-1);
    }
    }
  • 函数内部:this对象,引用的是函数执行的环境对象

  • caller属性:调用当前函数的引用
  • 严格模式下,arguments.callee、arguments.caller访问出错,不能给函数的caller属性赋值。非严格模式下arguments.caller=undefined。

    函数的属性和方法

  • 每个函数都包含两个属性:length和prototype
  • length:函数希望接受的命名参数的个数
  • prototype:保存所有实例方法的真正所在,toString()和valueOf()保存在其名下。prototype属性不可枚举。
  • 每个函数都包含两个非继承而来的方法:apply()和call()。
  • apply(运行函数的作用域,参数数组或arguments对象) call(运行函数的作用域,参数逐个列举)
  • apply()和call()的作用:1.传递参数 2.扩充函数赖以运行的作用域
  • bind():创建一个函数的实例
  • 函数的toString()、valueOf()、toLocaleString()方法都只返回函数代码

基本包装类型

  • 3个特殊的引用类型:Boolean、Number、String
  • 具有的共同特征

    1. 每个包装类型都映射到同名的基本类型
    2. 在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作
    3. 操作基本类型值的语句一旦执行完毕,就会立即销毁新创建的包装对象
  • 引用类型与基本包装类型的主要区别是对象的生存期:new操作符创建的引用类型的实例,在执行流离开当前作用域前都一直保存在内存中,自动创建的基本包装类型的对象,只存在于一行代码的执行瞬间,然后立即被销毁。

  • 使用new调用基本包装类型构造函数,与直接调用同名的转型函数是不一样的。
    1
    2
    3
    4
    5
    6
    var value="25";
    var number =Number(value); //转型函数
    alert(typeof number); //"number"

    var obj=new Number(value); //构造函数
    alert(typeof obj); //"object"

Boolean

  • 布尔表达式中所有的对象都会转换为true

    1
    2
    3
    var falseObject=new Boolean(false);
    var result=falseObject && true;
    alert(result); //true
  • 建议永不使用

    Number

  • toFixed():参数表示几位小数,小数表示
  • toExponential():参数表示几位小数,指数表示(e)
  • toPrecision():根据参数,自动选择最合适的数值表示格式
  • 建议永不使用

String

字符方法

  • charAt():参数是基于0的字符位置,返回字符。 也可以使用stringValue[]这样的形式。
  • carCodeAt():参数是基于0的字符位置,返回字符编码。

    字符串操作方法

  • concat():拼接字符串,对原字符串没有影响
  • slice(开始位置,结束位置):返回子字符串,有负数时与字符串的长度相加
  • substr(开始位置,返回的字符个数):返回子字符串,第一个负数参数加上字符串的长度,第二个负数参数转换为0
  • substring(开始位置,结束位置):返回子字符串,把手游的负值参数都转换为0

    字符串位置方法

  • index():从字符串的开头,搜索给定字符串
  • lastIndexOf():从字符串的末尾向前搜索子字符串,搜索给定字符串

    trim()方法

  • trim():创建字符串的副本,删除前置及后缀的所有空格,然后返回结果,对原字符串没有影响

字符串大小写转换

  • toLowerCase():转换成小写
  • toLocaleLowerCase():针对特定地区实现
  • toUpperCase():转换成大写
  • toLocaleUpperCase():针对特定地区实现

字符串的模式匹配方法

  • match():与exec()方法相同,参数是正则表达式或是RegExp对象,返回数组
  • search():参数是正则表达式或是RegExp对象,返回第一个匹配项的索引。
  • replace():替换。如果要替换所有的子字符串,需要制定全局g标志
  • split():基于制定的分隔符将一个字符串分割成多个字符串,并将结果放在数组中

localeCompare()方法

  • localeCompare():比较两个字符串。字符串在字母表中应该排在字符串参数之前,就返回一个负数,等于就返回0,之后就返回一个正数
  • 返回的数值取决于实现,而不一定是-1,0,1。

formCharCode()方法

  • formCharCode():接受一或多个字符编码,将它们转换成一个字符串

单体内置对象

  • 由ECMAScript实现提供的、不依赖于宿主环境的对象,这些对象在ECMAScriptz程序执行之前就已经存在了。也就是说,开发人员不必显式地实例化内置对象,因为它们已经实例化了
  • 内置对象:比如:Object、Array、String

Global对象

  • 不属于任何其他对象的属性和方法,都是它的属性和方法

URI编码方法

  • URI(Uniform Resource Identifiers):通用资源标识符
  • encodeURI():对整个URI编码,发送给浏览器
  • encodeURIComponent():主要用于对URI中的某一段进行编码,发送给浏览器
  • encodeURI()和encodeURIComponent()的区别:encodeURI()不会对本身属于URI的特殊字符进行编码,encodeURIComponent则会对它发现的任何非标准字符进行编码
  • 相对应的方法还有decodeURI()、decodeURIComponent()

eval()方法

  • 通过eval()执行的代码被认为是包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链。
  • 通过eval()执行的代码可以引用在包含环境中定义的变量
  • 严格模式下,外部访问不到eval()中创建的任何变量或函数

window对象

  • 访问Global对象有两种方法:
    1. window对象
    2. 返回this值取得Global对象
1
2
3
var global=function(){
return this;
}();

Math对象

min和max方法

  • 寻找数组中的最大最小值,可以使用apply()方法。关键在于把Math对象作为apply()的第一个参数,从而正确地设置this值。
    1
    2
    3
    var values=[1,2,3,4,5,6];
    var max=Math.max.apply(Math,values);
    alert(max);

舍入方法

  • Math.ceil():向上舍入
  • Math.floor():向下舍入
  • Math.round():标准舍入

random()方法

  • 值=Math.floor(Math.random()*可能值的总数+第一个可能的值)
    1
    2
    3
    4
    function selectForm(lowerValue,upperValue){
    var choices=upperValue-lowerValue+1;
    return Math.floor(Math.random()*choices+lowerValue);
    }

JavaScript高级程序设计读书笔记4

发表于 2018-01-12 | 分类于 读书笔记 , JavaScript高级程序设计

第4章 变量、作用域和内存问题

基本类型和引用类型的值

  • 基本数据类型保存在栈内存中,按值访问,引用数据类型保存在堆内存中,按引用访问。

    复制变量值

  • 复制引用类型的值时,两个变量引用同一个对象。改变其中一个变量,就会影响另一个变量
    1
    2
    3
    4
    var obj1=new Object();
    var obj2=obj1;
    obj1.name="hello";
    alert(obj2.name); //hello

传递参数

  • 把函数外部的值复制给函数内部的参数,是按值传递的。

    1
    2
    3
    4
    5
    6
    7
    8
    function addTen(num){
    num+=10;
    return num;
    }
    var count=20;
    var result=addTen(count);
    alert(count); //20,没有变化
    alert(result); //30
  • person按值传递给obj,指向的对象在堆内存中是同一个,而且是全局对象。

    1
    2
    3
    4
    5
    6
    function setName(obj){
    obj.name="hello";
    }
    var person =new Object();
    setName(person);
    alert(person.name); //"hello"
  • 以下例子说明,确实是按值传递。如果是按引用传递,那么person就会自动修改为指向name属性值的“Greg”对象。

    1
    2
    3
    4
    5
    6
    7
    8
    function setName(obj){
    obj.name="hello";
    obj=new Object();
    obj.name="Greg";
    }
    var person =new Object();
    setName(person);
    alert(person.name); //"hello"
  • 确定一个值是哪种基本类型可以使用typeof操作符。

  • 确定一个值是哪种引用类型可以使用instanceof操作符。
  • 如果变量是引用类型的实例,那么instanceof操作符就会返回true,如果是基本类型的值,那么instanceof操作符就会返回false。

执行环境与作用域

  • 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
  • 全局执行环境是最外围的一个执行环境。
  • 每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
  • 当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是,保证对执行环境有权访问的所有变量和函数的有序访问。
  • 作用域链的前端,始终是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。
  • 活动对象在最开始只包含一个变量,即arguments对象。
  • 作用域链的下一个变量对象来自包含(外部)环境,而在下一个变量对象则来自下一个包含环境。
  • 全局执行环境的变量对象始终都是作用域链中的最后一个对象。
  • 内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。

延长作用域链

  • 有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。在两种情况下会发生这种现象:

    try-catch语句中的catch块:创建一个新的变量对象
    with语句:会将指定的对象添加到作用域链中

没有块级作用域

  • JS没有块级作用域
  • 在if、for语句中初始化的变量,在执行完后,也依旧会存在于外部的执行环境中。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    if(true){
    var color="blue";
    }
    alert(color); //"blue"

    for(var i=0;i<10;i++){
    doSomething(i);
    }
    alert(i); //10

声明变量

  • 使用var声明的变量会自动被添加到最接近的环境中。

    1
    2
    3
    4
    5
    6
    function add(num1,num2){
    var sum=num1+num2;
    return sum;
    }
    var result=add(10,20); //30
    alert(sum); //由于sum不是有效的变量,因此会导致错误
  • 如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境

    1
    2
    3
    4
    5
    6
    function add(num1,num2){
    sum=num1+num2;
    return sum;
    }
    var result=add(10,20); //30
    alert(sum); //30

查询标识符

  • 当在环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么,沿作用域链向上搜索。

    1
    2
    3
    4
    5
    var color="blue";
    function getColor(){
    return color;
    }
    alert(getColor()); //"blue"
  • 如果局部环境中存在着同名标识符,就不会使用位于父环境中的标识符

    1
    2
    3
    4
    5
    6
    var color="blue";
    function getColor(){
    var color="red";
    return color;
    }
    alert(getColor()); //"red"

垃圾回收

  • 垃圾收集器会按照固定的时间间隔周期性地执行这一操作。
  • IE6声明达到任何一个临界值,垃圾收集器就会运行,IE7则声明垃圾收集例程回收的内存分配量低于15%,则临界值加倍,若高于15%,则将临界值重置为默认值。
  • 一旦数据不再有用,最好将值设置为NULL来释放引用,这个做法是解除引用,适用于大多数全局变量和全局对象的属性。
  • 解除引用不等于自动回收内存,真正作用是让值脱离执行环境,便于垃圾收集器下次运行时回收。

标记清除

  • JS中最常用的垃圾收集方式是标记清除。
  • 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后去掉环境中的变量以及被环境中搞得变量引用的变量标记,在此之后再被加上标记的变量将被视为准备删除的变量。因为环境中的的变量已经无法访问到这些变量了。最后垃圾收集器完成内存清除工作。

引用计数

  • 不太常用的垃圾收集策略是引用计数
  • 当生命的变量并将一个引用类型值赋给该变量时,则这个值的引用次数是1,如果同一个值又被赋给了另一个变量,则该值的引用次数加1,否则减1,直到这个值的引用次数为0
  • 存在循环引用的问题,对象A中包含一个指向对象B的指针,对象B也包含一个指向对象A的引用。最好的方式是在不适用的时候手工断开。
  • ObjectA.someOtherObject=null;
  • ObjectB.anotherObject=null;
1…3456
Xyy

Xyy

I want to get ahead in a step-by-step

59 日志
16 分类
8 标签
© 2018 Xyy
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4