IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 《高性能 JavaScript》第 2 章 数据访问 -> 正文阅读

[JavaScript知识库]《高性能 JavaScript》第 2 章 数据访问

第 2 章 数据访问

《高性能 JavaScript》—— Nicholas C. Zakas

存储数据的位置关系到代码执行过程中数据的检索速速。

JS 有 4 中基本的数据存储位置:

1.字面量,代表自身,有: 字符串、数字、布尔值、对象、数组、函数、正则、 nullundefined

2.变量,开发人员使用 var 定义的

3.数组元素,存储在 JS 数组中,以数字作为索引

4.对象成员,存储在 JS 对象中,以字符串作为索引

字面量和局部变量的访问速度较快。

1. 作用域

在函数里,作用域决定哪些变量可以访问 以及 this 如何赋值。

1.1. 作用域链

JS 中的函数是对象(Function 类的实例),
有一个内部属性 [[Scope]],这个属性即为 作用域链,它决定哪些数据可以被函数访问,
作用域链中元素称为 变量对象(variable object),由键值对组成。

当函数创建时,函数所在作用域的数据将会赋值给变量对象;
也就是说,定义函数的那个作用域的所有变量都会塞到 [[Scope]] 中的变量对象上。

如下:

function add(num1, num2) {
  var sum = num1 + num2;

  return sum;
}

以上代码执行完后,add() 函数被创建, 该函数所在作用域为全局作用域(顶级作用域),
全局作用域变量对象用 global object 表示,该变量对象中有 windowdocument、全局变量等,
如下图:

请添加图片描述

创建函数时的作用域链将在函数调用时用到,如下代码:

var total = add(5, 10);

执行 add 函数时,会创建一个内部对象,该内部对象称为 执行上下文(execution context)。
执行上下文 就是函数执行时的环境,每次调用函数都会创建一个新的执行上下文,
函数调用完毕后 执行上下文 会被销毁。

执行上下文 有自己的作用域链,用于解析标识符;
执行上下文 一旦创建,就用函数创建时的 [[Scope]] 会拷贝到自己的作用域链,
拷贝 [[Scope]] 后,会创建一个 activation object(活动对象)变量对象,并置于作用域链的顶端,
activation object 包含 局部变量、命名参数、arguments 对象、this
如下图所示:

请添加图片描述

1.2. 解析标识符

在函数执行时,遇到变量(标识符),会在 执行上下文 的作用域链中找,
找到了则返回,找不到则视为 undefined
在作用域链中查找标识符的快慢影响性能。

作用域链由一个个变量对象组成,每个变量对象包含一个作用域的标识符;
查找的标识符(所在变量对象),越靠前 查找越快。

当多个变量对象存在相同标识符时,取第一个。

标识符所在的变量对象越靠后,读写变量的速度就越慢,
局部变量最快,全局变量最慢。(Chrome 专门优化过,差别不大)。

建议尽量使用局部变量,将非当前作用域的变量存储到局部变量中,如下:

function initUI(){
  // 将多次查找的 document 存到局部变量中
  var doc = document;
  var bd = doc.body;
  var links = doc.getElementsByTagName("a");
  var i= 0;
  var len = links.length;
 
  while(i < len){
    update(links[i++]);
  }

  doc.getElementById("go-btn").onclick = function(){
    start();
  };

  bd.className = "active";
}

1.3. 作用域链扩充

一般 执行上下文 的作用域链不会改变,但 withtry catch 会扩充作用域链。

使用 with(obj) 语句会将 obj 作为变量对象插入到作用域链的顶部,如下:

function initUI(){
  with (document){ //avoid!
    var bd = body,
        links = getElementsByTagName("a"),
        i= 0,
        len = links.length;
    while(i < len){
      update(links[i++]);
    }
    getElementById("go-btn").onclick = function(){
      start();
    };
    bd.className = "active";
  }
}

作用域链,如下:

请添加图片描述

此时,虽然访问 document 中的属性(方法)会非常快,但局部变量的访问变慢了,最好不要使用 with 语句。

try catch 也是一样,代码如下:

try {
  methodThatMightCauseAnError();
} catch (ex){
  alert(ex.message); //scope chain is augmented here
}

当发生错误时,会执行 catch 子句,此时会将异常对象 ex 所在的变量对象插入到作用域链的顶部;
建议不要在 catch 子句中执行大量逻辑。

1.4. 动态作用域

function execute(code) {
  eval(code);

  function getW(){
    return window;
  }

  var w = getW();
  //what value is w?
};

// 只有当代码执行时,
// 才能知道 w 的值是全局作用域的 window(BOM 对象) 还是局部作用域的 window(值为 {})
execute("var window = {};");

withtry catch 、函数中的 eval() 都是动态作用域,
因为在代码执行时才能确定其变量对象,所以 JS 引擎的静态代码分析也就不起作用了。
因此,在确定有必要时才使用它们。

1.5. 闭包、作用域、内存

闭包是函数的特性,(有时称函数为闭包)
闭包能使函数访问局部作用域之外的数据,(闭包的 [[Scope]] 是外层函数执行上下文的作用域链)
一般在嵌套函数中,如下:

function assignEvents(){
  var id = "xdi9592";

  document.getElementById("save-btn").onclick = function handler(event) {
    saveDocument(id);
  };
}

handler 函数就是一个闭包(函数),
assignEvents() 执行时,handler 函数被创建,
assignEvents 的执行上下文的作用域链中的所有变量对象会添加到 handler 函数的 [[Scope]]
如下图:

请添加图片描述

由于闭包的[[Scope]]会包含外层函数的执行上下文的作用域链,
会导致外层函数执行完毕后执行上下文不能销毁,需要更多的内存开销。

handler 执行时,[[Scope]] 中的变量对象会拷贝到执行上下文的作用域中,如下图:

请添加图片描述

在闭包中,如果频繁访问外层函数的活动对象(Activation object)会有性能问题。

2. 对象成员

JS 对象包括自定义的和内置的(DOM、BOM 等)。

访问 JS 中的对象的成员的速度比字面量或变量慢。

2.1. 原型

传统面向对象语言(如 Java)通过类来定义新对象的属性和方法;
而 JS 中的新对象是基于原型对象的,类似于新对象继承原型对象的属性和方法。

对象中有一个 __proto__ 属性,用于获取(或设置)原型对象。

对象中的属性分为两类: 对象本身的(实例成员/自有属性),对象原型上的(原型成员)。
如下:

// book 对象有两个自有属性 title、publisher
var book = {
  title: "High Performance JavaScript",
  publisher: "Yahoo! Press"
};

// toString() 是其原型上的成员
console.log(book.toString()); // "[object Object]"

如下图:

请添加图片描述

解析对象成员的过程跟解析变量的非常相似,
先在对象本身上找,没找到则到对象的原型上去找,找到则返回。

可以通过 Object.prototype.hasOwnProperty(name) 判断对象上是否有指定自有成员,如下:

var book = {
  title: "High Performance JavaScript",
  publisher: "Yahoo! Press"
};

console.log(book.hasOwnProperty("title")); //true
console.log(book.hasOwnProperty("toString")); //false
console.log("title" in book); //true
console.log("toString" in book); //true

2.2. 原型链

(默认情况下所有对象都是 Object 类的实例。)

对象的原型决定该对象的类型,(类型即原型),可以通过构造函数来创建新的类型。
如下:

// Book 构造函数用于创建新的 Book 的实例
function Book(title, publisher){
  this.title = title;
  this.publisher = publisher;
}

Book.prototype.sayTitle = function(){
 alert(this.title);
};

var book1 = new Book("High Performance JavaScript", "Yahoo! Press");
var book2 = new Book("JavaScript: The Good Parts", "Yahoo! Press");

// Book 的实例 book1 的原型是 Book.prototype
alert(book1 instanceof Book); //true

// Book.prototype.prototype 是 Object
alert(book1 instanceof Object); //true

book1.sayTitle(); //"High Performance JavaScript"
alert(book1.toString()); //"[object Object]"

原型链,如下图:

请添加图片描述

所有的 Book 的实例共享相同的原型链。

原型成员越深,查找速度越慢。

2.3. 嵌套成员

对象成员也是对象,则会形成嵌套成员,如 window.location.href

嵌套得越深,查找速度越慢。

2.4. 缓存对象成员的值

在同一个函数里,不要多次查找同一个对象的成员,如下:

function hasEitherClass(element, className1, className2){
  return element.className == className1 || element.className == className2;
}

element.className 被访问了两次,查找对象成员的过程也执行了两次,
最好使用局部变量保存对象成员的值:

function hasEitherClass(element, className1, className2){
  var currentClassName = element.className;
  return currentClassName == className1 || currentClassName == className2;
}

不建议保存成员方法(函数),因为方法大多依赖 this

3. 总结

在 JS 中,数据存储的位置有四种: 字面量、变量、数组元素、对象成员。

  • 访问 字面量、局部变量 速度最快
  • 局部变量位于作用域链的顶端,查找速度快;全局变量位于作用域链的低端,查找速度慢。
  • 不要使用 with,它会改变 activation object
  • 嵌套的对象成员,嵌套得越深查找速度越慢
  • 成员属性在原型链中的位置越深,查找速度越慢
  • 可以将查找速度慢的数据缓存起来
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-29 11:32:24  更:2021-07-29 11:33:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 5:32:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码