JavaScript高级程序设计笔记

1,148 阅读11分钟

(持续更新中。。。)

第四章 - 变量、作用域与内存

目标

  • 通过变量使用原始值与引用值
  • 理解执行上下文
  • 理解垃圾回收

原始值与引用值

  • 原始值: Undefined、Null、Boolean、Number、String 和 Symbol。按值(by value)访问。
// 包装类
let name = "Nicholas";
name.age = 27;
console.log(name.age); // undefined 

  • 引用值: 按引用(reference)访问,引用值可以随时添加、修改和删除其属性和方法。
  • 复制值

原始值赋值到另一个变量时,原始值会被复制到新变量的位置。引用值赋值到另一个变量时,复制的值实际上是一个指针,它指向存储在堆内存中的对象。

  • 传递参数 函数的参数都是按值传递的,相当于复制
// 原始值
function addTen(num) {
 num += 10;
 return num;
}
let count = 20; 
86 第 4 章 变量、作用域与内存
let result = addTen(count);
console.log(count); // 20,没有变化
console.log(result); // 30 
// 引用值
function setName(obj) {
 obj.name = "Nicholas";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"

执行上下文与作用域

每个上下文都有一个关联的变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据会用到它。上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数。每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain)。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。如果上下文是函数,则其活动对象(activation object)用作变量对象。活动对象最初只有一个定义变量:arguments。(全局上下文中没有这个变量。)作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文。以此类推直至全局上下文;全局上下文的变量对象始终是作用域链的最后一个变量对象。

  • 全局上下文

  • 在浏览器中,全局上下文就是我们常说的 window 对象

垃圾回收

  • 基本思路:确定哪个变量不会再使用,然后释放它占用的内存。

  • 标记策略:标记清理和引用计数。

  • 标记清理(常用):当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。当变量离开上下文时,也会被加上离开上下文的标记。

  • 引用计数: 是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一个值的引用数为 0 时,垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存。

  • 性能 垃圾回收程序会周期性运行,如果内存中分配了很多变量,则可能造成性能损失,因此垃圾回收的 时间调度很重要。

  • 隐藏类和删除操作

    运行期间,V8 会将创建的对象与隐藏类关联起来,以跟踪它们的属性特征。能够共享相同隐藏类 的对象性能会更好

    // low 不同隐藏类
    function Article() {
     this.title = 'Inauguration Ceremony Features Kazoo Band';
    }
    let a1 = new Article();
    let a2 = new Article(); 
    a2.author = 'Jake'; 
    
    
    // good 相同隐藏类
    function Article(opt_author) {
     this.title = 'Inauguration Ceremony Features Kazoo Band';
     this.author = opt_author;
    }
    let a1 = new Article();
    let a2 = new Article('Jake'); 
    
    

    动态删除属性与动态添加属性导致的后果一样。

    // low
    function Article() {
     this.title = 'Inauguration Ceremony Features Kazoo Band';
     this.author = 'Jake';
    }
    let a1 = new Article();
    let a2 = new Article();
    delete a1.author; 
    
    
    function Article() {
     this.title = 'Inauguration Ceremony Features Kazoo Band';
     this.author = 'Jake';
    }
    let a1 = new Article();
    let a2 = new Article();
    a1.author = null; 
    
    
  • 内存泄漏

    • 意外声明全局变量
    
    function setName() {
      name = 'Jake';
    } 
    
    
    • 定时器
    
    let name = 'Jake';
    setInterval(() => {
     console.log(name);
    }, 100); 
    
    
    • 使用 JavaScript 闭包
    
    let outer = function() {
     let name = 'Jake';
     return function() {
     return name;
     };
    };
    
    
  • 静态分配与对象池

第十三章 - 客户端检测

目标

  • 使用能力检测
  • 用户代理检测的历史
  • 软件与硬件检测
  • 检测策略

能力检测

能力检测(又称特性检测)即在 JavaScript 运行时中使用一套简单的检测逻辑,测试浏览器是 否支持某种特性。

// 例
if (object.propertyInQuestion) {
 // 使用 object.propertyInQuestion
} 

安全能力检测

// 例
// 好一些,检测 sort 是不是函数
function isSortable(object) {
 return typeof object.sort == "function";
} 

基于能力检测进行浏览器分析


// 检测浏览器是否支持 Netscape 式的插件
let hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);
// 检测浏览器是否具有 DOM Level 1 能力
let hasDOM1 = !!(document.getElementById && document.createElement &&
 document.getElementsByTagName); 

通过检测一种或一组能力,并不总能确定使用的是哪种浏览器。 能力检测最适合用于决定下一步该怎么做,而不一定能够作为辨识浏览器的标志。

用户代理检测

用户代理检测通过浏览器的用户代理字符串确定使用的是什么浏览器。用户代理字符串包含在每个HTTP 请求的头部,在 JavaScript 中可以通过 navigator.userAgent 访问。


navigator.userAgent

识别浏览器与操作系统

  • 识别浏览器开发商信息

navigator.vendor
// "Google Inc."

  • 识别浏览器所在的操作系统

navigator.platform
// "MacIntel"

  • screen.colorDepth 和 screen.pixelDepth 返回显示器每像素颜色的位深

console.log(screen.colorDepth); // 24
console.log(screen.pixelDepth); // 24 

  • Geolocation API 暴露当前设备的地理位置。

navigator.geolocation
// Geolocation {}

  • 连接属性

navigator.connection

  • 获取电池信息 有四个监听电池信息变化的事件
  • onchargingchange // 加充电状态变化时
  • onchargingtimechange // 加充电时间变化时
  • ondischargingtimechange // 加放电时间变化时
  • onlevelchange // 加电量百分比变化时
navigator.getBattery().then((b) => console.log(b)); 

  • 处理器核心数

navigator.hardwareConcurrency
// 8

  • 设备内存大小(512MB 返回 0.5,4GB 返回 4)

navigator.deviceMemory
// 8

  • 最大触点数

电脑没有触屏为 0


navigator.maxTouchPoints
// 0

第十七章 - 事件

目标

  • 理解事件流
  • 使用事件处理程序
  • 了解不同类型的事件

事件流

  • 事件流描述了页面接收事件的顺序。

  • 事件冒泡:从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)

  • 事件捕获: 事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。

  • 通常建议使用事件冒泡,特殊情况下可以使用事件捕获。

DOM 事件流

事件捕获、到达目标和事件冒泡。

事件处理程序

  • 以"on"开头: onclick
  • HTML中

<input type="button" value="Click Me" onclick="console.log('Clicked')"/> 

  • DOM0 中

let btn = document.getElementById("myBtn");
btn.onclick = function() {
 console.log("Clicked");
}; 

  • DOM2 中

let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
 console.log(this.id);
}, false); 

  • IE 中

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
 console.log("Clicked");
});

  • 跨浏览器事件处理程序
// 新建 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

let btn = document.getElementById("myBtn")
let handler = function() {
 console.log("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
// 其他代码
EventUtil.removeHandler(btn, "click", handler);

事件对象

属性/方法类 型读/写说 明
bubbles布尔值只读表示事件是否冒泡
cancelable布尔值只读表示是否可以取消事件的默认行为
currentTarget元素只读当前事件处理程序所在的元素
defaultPrevented布尔值只读true 表示已经调用 preventDefault()方法(DOM3Events 中新增)
detail整数只读事件相关的其他信息
eventPhase整数只读表示调用事件处理程序的阶段:1 代表捕获阶段,2 代表到达目标,3 代表冒泡阶段
preventDefault()函数只读用于取消事件的默认行为。只有 cancelable 为 true 才可以调用这个方法
stopImmediatePropagation()函数只读用于取消所有后续事件捕获或事件冒泡,并阻止调用任何后续事件处理程序(DOM3 Events 中新增)
stopPropagation()函数只读用于取消所有后续事件捕获或事件冒泡。只有 bubbles为 true 才可以调用这个方法
target元素只读事件目标
trusted布尔值只读true 表示事件是由浏览器生成的。false 表示事件是开发者通过 JavaScript 创建的(DOM3 Events 中新增)
type字符串只读被触发的事件类型
ViewAbstractView只读与事件相关的抽象视图。等于事件所发生的 window 对象

在事件处理程序内部,this 对象始终等于 currentTarget 的值,而 target 只包含事件的实际 目标。

// 例
// 在 body 添加点击事件,作用域btn
document.body.onclick = function(event) {
 console.log(event.currentTarget === document.body); // true
 console.log(this === document.body); // true
 console.log(event.target === document.getElementById("myBtn")); // true
};

  • preventDefault()方法用于阻止特定事件的默认动作。(需 cancelable 为 true)

  • stopPropagation()方法用于立即阻止事件流在 DOM 结构中传播,取消后续的事件捕获或冒泡。

用户界面事件

  • load:在 window 上当页面加载完成后触发
window.addEventListener("load", (event) => {
 console.log("Loaded!");
}); 
  • error:在 window 上当 JavaScript 报错时触发,在<img>元素上当无法加载指定图片时触发
  • select:在文本框(<input>或 textarea)上当用户选择了一个或多个字符时触发。
  • resize:在 window 或窗格上当窗口或窗格被缩放时触发。
  • scroll:当用户滚动包含滚动条的元素时在元素上触发。<body>元素包含已加载页面的滚动条。大多数 HTML 事件与 window 对象和表单控件有关。
window.addEventListener("scroll", (event) => {
 if (document.compatMode == "CSS1Compat") {
 console.log(document.documentElement.scrollTop);
 } else {
 console.log(document.body.scrollTop);
 }
}); 

焦点事件

  • blur:当元素失去焦点时触发。这个事件不冒泡
  • focus:当元素获得焦点时触发。这个事件不冒泡

鼠标和滚轮事件

  • click:在用户单击鼠标主键(通常是左键)或按键盘回车键时触发。
  • dblclick:在用户双击鼠标主键(通常是左键)时触发。
  • mousedown:在用户按下任意鼠标键时触发。这个事件不能通过键盘触发。
  • mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发。
  • mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发。
  • mousemove:在鼠标光标在元素上移动时反复触发。
  • mouseout:在用户把鼠标光标从一个元素移到另一个元素上时触发。
  • mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。
  • mouseup:在用户释放鼠标键时触发。

。。。 。。。 。。。 下次再写,太多了

第十九章 - 表单脚本

目标

  • 理解表单基础
  • 文本框验证与交互
  • 使用其他表单控件

基础

  • Web 表单在 HTML 中以<form>元素表示,在 JavaScript 中则以 HTMLFormElement 类型表示。

  • 自带的属性方法

    • action:请求的 URL,等价于 HTML 的 action 属性。
    • enctype:请求的编码类型,等价于 HTML 的 enctype 属性。
    • method:HTTP 请求的方法类型,通常是"get"或"post",等价于 HTML 的 method 属性。
    • reset():把表单字段重置为各自的默认值。
    • submit():提交表单。
  • 提交表单 && 重置表单

let form = document.getElementById("myForm"); 
// 提交表单
form.submit(); 
// 重置表单
form.reset(); 

  • 方法

    • focus()和 blur(), focus聚焦
    // 失焦
    document.forms[0].elements[0].blur(); 
    
  • 事件

    • focus, blur, change
    textbox.addEventListener("change", (event) => {
      let target = event.target;
    }); 
    

文本框

  • input
// size 宽度
<input type="text" size="25" maxlength="50" value="initial value">

  • textarea
// rows 高度 , cols 高度
<textarea rows="25" cols="5">initial value</textarea> 
  • 选择文本
// 1
let textbox = document.forms[0].elements["textbox1"];
textbox.select(); 
// 2
textbox.addEventListener("focus", (event) => {
 event.target.select();
}); 

选择框

  • select和option
<select name="location" id="selLocation">
 <option value="Sunnyvale, CA">Sunnyvale</option>
 <option value="Los Angeles, CA">Los Angeles</option>
 <option value="Mountain View, CA">Mountain View</option>
 <option value="">China</option>
 <option>Australia</option>
</select> 
  • 选中第一个
selectbox.options[0].selected = true; 
  • 元素 contenteditable
// 可以被编辑
<div  contenteditable></div>

第二十四章 - 网络请求与远程资源

目标

  • 使用 XMLHttpRequest 对象
  • 处理 XMLHttpRequest 事件
  • 源域 Ajax 限制
  • Fetch API
  • Streams API

XMLHttpRequest

// 创建
let xhr = new XMLHttpRequest(); 

// 0: open 之前,1: open 之后 2.send 之后 3.:接收中 4. 完成
xhr.onreadystatechange = function() {
 if (xhr.readyState == 4) {
 if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
 alert(xhr.responseText);
 } else {
 alert("Request was unsuccessful: " + xhr.status);
 }
 }
}; 
// get , post 等
xhr.open("get", "example.php", false); 
// send 请求体,没有必须穿 null
xhr.send(null); 
  • 请求头
    • Accept:浏览器可以处理的内容类型。
    • Accept-Charset:浏览器可以显示的字符集。
    • Accept-Encoding:浏览器可以处理的压缩编码类型。
    • Accept-Language:浏览器使用的语言。
    • Connection:浏览器与服务器的连接类型。
    • Cookie:页面中设置的 Cookie。
    • Host:发送请求的页面所在的域。
    • User-Agent:浏览器的用户代理字符串。
xhr.setRequestHeader("MyHeader", "MyValue"); 
  • FormData
let data = new FormData();
data.append("name", "Nicholas"); 
  • 超时
xhr.timeout = 1000; // 设置 1 秒超时
xhr.ontimeout = function() {
 alert("Request did not return in a second.");
}; 
  • 跨源资源共享 CORS

  • 预检请求

    • 求使用 OPTIONS 方法发送
  • JSONP

    • JSONP 是“JSON with padding”的简写
function handleResponse(response) {
 console.log(`
 You're at IP address ${response.ip}, which is in
 ${response.city}, ${response.region_name}`);
}
let script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild); 

Fetch

let r = fetch('/bar', {
 method: 'POST', // 发送请求体时必须使用一种 HTTP 方法
 body: payload,
 headers: jsonHeaders
});
console.log(r); // Promise <pending>
fetch('bar.txt')
 .then((response) => {
   console.log(response.status); // 200
   console.log(response.statusText); // OK 
   response.text().then((data) => {
   console.log(data);
 });
 }); 
  • 中断
let abortController = new AbortController();
fetch('wikipedia.zip', { signal: abortController.signal })
 .catch(() => console.log('aborted!');
// 10 毫秒后中断请求
setTimeout(() => abortController.abort(), 10);
// 已经中断 

Web Socket

let socket = new WebSocket("ws://www.example.com/server.php"); 

let stringData = "Hello world!";
let arrayBufferData = Uint8Array.from(['f', 'o', 'o']);
let blobData = new Blob(['f', 'o', 'o']);
socket.send(stringData);
socket.send(arrayBufferData.buffer);
socket.send(blobData);
// 接收
socket.onmessage = function(event) {
 let data = event.data;
 // 对数据执行某些操作
}; 

  • 状态
    • WebSocket.OPENING(0):连接正在建立。
    • WebSocket.OPEN(1):连接已经建立。
    • WebSocket.CLOSING(2):连接正在关闭。
    • WebSocket.CLOSE(3):连接已经关闭。
    • 其他事件
    • open:在连接成功建立时触发。
    • error:在发生错误时触发。连接无法存续。
    • close:在连接关闭时触发。

安全

  • 要求通过 SSL 访问能够被 Ajax 访问的资源。
  • 要求每个请求都发送一个按约定算法计算好的令牌(token)。注意,以下手段对防护 CSRF 攻击是无效的。
  • 要求 POST 而非 GET 请求(很容易修改请求方法)。
  • 使用来源 URL 验证来源(来源 URL 很容易伪造)。
  • 基于 cookie 验证(同样很容易伪造)。