《单页Web应用--温故JavaScrpt》学习笔记整理
变量作用域,函数提升和执行环境对象
1. 变量作用域
在 JavaScript 中,变量
的 作用域
由 函数
限定,即:唯一能定义变量作用域的语块就是 函数
。
变量
要么是全局的,要么是局部的。
- 全局变量:在函数外部定义,处处可以访问;
- 局部变量:在函数内部定义,只有在声明它的地方才能访问。
ES6 引入块级作用域——>
let
语句
let (prisoner = 'I am in prison!') {
console.log( prisoner ); // "I am in prison!"
}
console.log( prisoner ); // Erroe:prisoner is not defined"
2. 变量提升
在 JavaScript 中,当变量被声明时,声明会被提升到它所在函数的顶部,并被赋予 undefined 值。这就使得在函数的任意位置声明的变量存在于整个函数中,尽管在赋值之前,它的值一直为 undefined。
function prison () {
console.log(prisoner); // "undefined"
var prisoner = 'Now I am defined!';
console.log(prisoner); // "Now I am defined!"
}
prison();
- 因为变量声明总是被提升到函数作用域的顶部,所以在函数的顶部声明变量总是最好的做法,更好的是使用单个
var
语句,这样可以和 JavaScript 的做法保持一致。
作用域和变量提升结合
[例1:]
var name = 'Joe';
function prison () {
console.log(name); // "Joe"
}
prison();
[例2:变量在声明前是未定义的]
var name = 'Joe';
function prison () {
console.log(name); // "undefined"
var name; // name的声明被提升到函数的顶部,在查找全局作用域的name之前,会先检查这一被提升的声明
}
prison();
[例3:变量在声明前有值]
// 变量作为参数传入
var name = 'Joe';
function prison ( name ) {
console.log(name); // "Bob"
var name; // 变量name已经由参数赋值,当声明它时,不会用undefined值覆盖。这里的声明是多余的。
console.log(name); // "Bob"
}
prison( 'Bob' );
3. 高级变量提升和执行环境对象
提升
JavaScript 引擎在进入作用域时,会对代码分两轮处理:(1)初始化变量;(2)执行代码。
第一轮初始化变量,JavaScript 引擎分析代码,并做了以下3件事情:
1)声明并初始化函数参数;
2)声明局部变量,包括将匿名函数赋给一个局部变量,但并不初始化他们;
3)声明并初始化函数。
在第一轮,局部变量并未被赋值,因为可能需要在代码执行后才能确定它的值,而第一轮不会执行代码。
参数被赋值了,因为在向函数传递参数之前,任何决定参数值的代码都可以运行了。
执行环境和执行环境对象——>理解 javaScript 引擎在第一轮是如何保存变量的
JavaScript 引擎把变量作为一个属性保存在一个对象上,这个对象称为 执行环境对象
。
执行环境
:每当函数被调用的时候,就会产生一个新的执行环境,即,指函数的执行。函数声明
:描述了当函数执行的时候会发生什么事情。- 属于执行环境部分的变量和函数,被保存在
执行环境对象
中,执行环境对象是对执行环境的 ECMA 标准实现。
执行环境
是一种概念,是运行中的函数的意思,由函数在执行时发生的所有事物组成,它不是对象。
执行环境
和函数声明
是分离的。
所有在函数中定义的 变量
和 函数
都是 执行环境
的一部分。当谈论函数的 作用域
时,执行环境
也是其所指的一部分。如果变量在当前执行环境中可访问,则变量在作用域内(即:如果在函数运行时变量可访问,则该变量在作用域内)。
在 JavaScript 引擎中,执行环境对象
是一种对象,属于 JavaScript 实现层面的东西,在开发的时候无法直接访问。间接地访问执行环境对象是很容易的,因为每次使用变量,就是在访问执行环境对象的属性。
由于可以在执行环境中调用函数,会产生很多层的深度。在执行环境中调用函数,会创建一个新的嵌套在已存在的执行环境内的执行环境。
JavaScript 引擎在执行环境对象中访问作用域内的变量,查找的顺序叫作 作用域链
,它和 原型链
一起,描述了 JavaScript 访问变量和属性的顺序。
解释变量作用域链以及为什么要使用它们
作用域是很微妙的,像嵌套执行环境。更准确地讲,可以把变量作用域看作链 ——> 作用域链
。
在运行期,JavaScript 会检索作用域层级来解析变量名。
它从当前作用域开始,然后按它的查找方式回到顶级的作用域,即 window(浏览器)或者 global(node.js)对象。
它使用找到的第一次匹配并停止查找。
在层级更深的嵌套作用域中的变量,会使用它们的当前作用域替换更加全局的作用域,从而隐藏更加全局的作用域中的变量。
使用原型创建 JavaScript 对象
JavaScript 对象是基于原型的,而当今其他广泛使用的语言全部都使用基于类的对象。
区别:
在基于类的系统中,定义对象:使用类来描述它是什么样子的。
类比:如果建筑是基于类的系统,则建筑师会先画出房子的蓝图,然后房子都按照蓝图来建造;
在基于原型的系统中,我们创建的对象,看起来要像我们想要的所有这种类型的对象那样,然后告诉JavaScript引擎,我们想要更多像这样的对象。
类比:如果建筑是基于原型的系统,则建筑师先建一所房子,然后将房子都建成像这种模样的。
简单对象创建:类和原型的比较
- 基于原型的对象更简单,并且当只有一个对象实例时,编写更快,只要在适当的地方简单地定义它就行了,它也支持更复杂的使用情况,使多个对象共享相似的特性。
- 在基于类的系统中,你得先定义类,定义构造函数,然后实例化对象,该对象是这个类的实例。
多个对象:类和原型的比较
[基于类的]:
/* step 1:定义类*/
public class Prisoner {
public int sentence = 4;
public int probation = 2;
public String name;
public String id;
/* step 2:定义类的构造函数*/
public Prisoner( String name, String id ) {
this.name = name;
this.id = id;
}
public static void main( String []args ) {
/* step 3:实例化对象*/
Prisoner firstPrisoner = new Prisoner("Joe", "12A");
Prisoner secondPrisoner = new Prisoner("Sam", "2BC");
System.out.println(firstPrisoner.name);
}
}
[基于原型的]:
// step 1:定义原型对象
var proto = {
sentence : 4,
probation : 2
};
// step 2:定义对象的构造函数
var Prisoner = function(name, id) {
this.name = name;
this.id = id;
}
// step 3:将构造函数关联到原型
Prisoner.prototype = proto;
// step 4:实例化对象
var firstPrisoner = new Prisoner('Joe', '12A');
var secondPrisoner = new Prisoner('Sam', '2BC');
补充说明:
1)在每个方法中,首先创建了 对象的模版
,作为创建对象的结构。模板在基于类的编程中叫做 类
,在基于原型的编程中叫作 原型对象
。
2)然后,创建了 构造函数
。在基于类的语言中,构造函数是在类的内部定义的,这样的话,当实例化对象时,哪个构造函数与哪个类匹配,就很清晰了。在 JavaScript 中,对象的构造函数和原型是分开设置的,所以不需要额外多一步来将它们连接在一起。
3)最后,实例化对象。
JavaScript 使用了
new
操作符,违背了它基于原型的核心思想,可能是试图让熟悉基于类继承的开发人员更容易理解。用Object.create
方法作为new
操作符的替代方案,来创建 JavaScript 对象,能增添一种更像是基于原型的感觉。
/**
* Object.create() 方法:
* 把原型作为参数并返回一个对象,使用这种方式,可以在原型对象上定义共同的属性和方法,
* 然后使用它来创建多个共享相同属性的对象。
*/
var proto = {
sentenct : 4,
probation : 2
}
var firstPrisoner = Object.create( proto );
firstPrisoner.name = 'Joe';
firstPrisoner.id = '12A';
var secondPrisoner = Object.create( proto );
secondPrisoner.name = 'Sam';
secondPrisoner.id = '2BC';
Object.create() 的改进方案:使用工厂函数来创建并返回最终的对象。
/**
* 使用 Object.create() 和工厂函数
*/
var proto = {
sentenct : 4,
probation : 2
}
var makePrisoner = function( name, id ) {
var prisoner = Object.create( proto );
prisoner.name = name;
prisoner.id = id;
return prisoner;
}
var firstPrisoner = makePrisoner( 'Joe', '12A' );
var secondPrisoner = makePrisoner( 'Sam', '2BC' );
Object.create() 方法:创建对象的最佳方法,它清晰地说明了原型是如何被设置的(有兼容性问题)。
new
操作符:创建对象的最常用方法,但是它遮掩了原型系统的细微差别。
// Cross-browser method to support Object.create()
var objectCreate = function ( arg ) {
if ( ! arg ) { return {}; }
function obj() {};
obj.prototype = arg;
return new obj;
};
Object.create = Object.create || objectCreate;
编写自执行匿名函数
1. 函数和匿名函数:
// 声明函数
function prison () {}
// 使用变量来保存函数
var prison = function prison () {};
// 用局部变量来保存的匿名函数
var prison = function () {};
// 调用方式都相同
prison();
2. 自执行匿名函数:
抛出问题:在 JavaScript 中,在全局作用域中定义的所有东西在每个地方都是可用的。有时候你不想和所有人共享,不想第三方库共享它们的内部变量,因为这很容易覆盖对象的库,从而导致难以诊断的问题。
解决方法:
1)把整个程序封装在函数中,然后调用这个函数,这样外部代码就不能访问到变量了——>冗长和不灵活;
2)自执行匿名函数(因为定义它时没有名字并且没有保存给变量,但却立即执行了)——>推荐。
优点:用来控制变量的作用域,阻止变量泄漏到代码的其他地方。
用途:可用于创建 JavaScript 插件,不会和应用代码冲突,因为它不会向全局名字空间添加任何变量。
[给匿名函数传参和普通函数传参对比]:
// 普通函数传参
var eatFunction = function (what_to_eat) {
var sentence = 'I am going to eat a ' + what_to_eat;
console.log( sentence );
}
eatFunction( 'sandwich' ); // I am going to eat a sandwich
// 自执行匿名函数传参
(function (what_to_eat) {
var sentence = 'I am going to eat a ' + what_to_eat;
console.log( sentence );
})('hotdog'); // I am going to eat a hotdog
[常用例子]:
// 保证函数作用域里面,$ 是 jQuery 对象
( function ( $ ) {
console.log( $ );
} )( jQuery );
使用模块模式和私有变量
抛出问题:虽然我们可以把应用封装在自执行匿名函数中,使应用免受第三方库(和我们自己)的影响,但是单页应用和庞大,不能定义在一个文件中。
解决思路:将文件分成一个个的模块,每个模块都有它们自己的私有变量。
/**
* 匿名函数没有保存在 prison 变量中,因为匿名函数被执行了
* 匿名函数的返回值保存在 prison 中
*/
var prison = (function () {
var prisoner_name = 'Mike Mikowski',
jail_term = '20 year term';
return {
prisoner: prisoner_name + ' - ' + jail_term,
sentence: jail_term
};
})();
console.log( prison.prisoner_name ); // "undefined"
console.log( prison.prisoner ); // "Mike Mikowski - 20 year term"
console.log( prison.sentence ); // "20 year term"
/**
* jail_pterm 不是 prison 对象或者原型上的属性,它是执行环境中创建的对象变量
* prison 变量保存了这个变量,并且执行环境已不复存在,因为函数已经执行结束。
*/
console.log( prison.jail_term ); // "undefined"
prison.jail_term = 'Sentence commuted';
console.log( prison.jail_term ); // "Sentence commuted"
console.log( prison.prisoner ); // "Mike Mikowski - 20 year term"
在稍大一点的模块中,减少全局变量是很重要的。
一旦自执行匿名函数停止执行,在它里面定义的变量没有了,所以它们是不能被更改的,所以它们无法通过 prison 变量访问到。它们用来定义匿名函数的返回对象上的 prison 和 sentence 属性,并且这些属性可以在 prison 变量上访问到。
prisoner_name
和jail_term
这些属性只在匿名函数执行时设置了一次,永远不会被更新。它们像是prison
对象的私有变量,只能通过匿名函数返回的对象上的方法来访问,不能在该对象或者原型上直接访问。为了能更新“私有变量”,我们必须把属性转变为方法,每次调用它们时都会访问变量。
var prison = (function () {
var prisoner_name = 'Mike Mikowski',
jail_term = '20 year term'; return {
prisoner: function () {
return prisoner_name + ' - ' + jail_term;
},
setJailTerm: function ( term ) {
jail_term = term;
}
};
})(); console.log( prison.prisoner() ); // "Mike Mikowski - 20 year term" prison.setJailTerm( 'Sentence commuted' ); console.log( prison.prisoner() ); // "Mike Mikowski - Sentence commuted"
探索闭包的乐趣和好处
1. 什么是闭包:
JavaScript 有 垃圾回收器
:当函数执行完毕时,管理内存的本地方法会将函数中所有创建了的东西从内存中移除。
闭包是阻止垃圾回收器将变量从内存中移除的方法,使得在创建变量的执行环境的外面能够访问到该变量。
var prison = (function () {
var prisoner = 'Josh Powell';
/**
* 在 prisoner 函数被保存到 prison 对象上时,一个闭包就创建了。
* 闭包因保存函数而被创建,在执行环境的外面,可以动态访问 prisoner 变量,
* 这就阻止了垃圾回收器将 prisoner 变量从内存中移除。
*/
return {
prisoner: function () {
return prisoner;
}
};
})();
prison.prisoner(); // "Josh Powell"
[闭包示例1]:
var makePrisoner = function ( prisoner ) {
return function () {
return prisoner;
}
};
var joshPrison = makePrisoner( 'Josh Powell' );
var mikePrison = makePrisoner( 'Mike Mikowski' );
console.log( joshPrison() ); // "Josh Powell"
console.log( mikePrison() ); // "Mike Mikowski"
[闭包示例2]:保存变量以便在 Ajax 请求返回时使用。
/**
* 当使用 JavaScript 对象中的方法时,this 指向这个对象。
*/
var prison = {
names: 'Mike Mikowski and Josh Powell',
who: function () {
return this.names;
}
};
prison.who(); // "Mike Mikowski and Josh Powell"
/**
* 如果是 jQuery 来发送 Ajax 请求的方法,则 this 不再指向对象,它指向 Ajax 请求对象
*/
var prison = {
names: 'Mike Mikowski and Josh Powell',
who: function () {
$.ajax({
success: function () {
console.log( this.names );
}
});
}
};
// 'this' is the ajax object
prison.who(); // "undefined"
闭包由函数创建,该函数在当前执行环境中访问了某个变量,并将该函数保存给当前执行环境外的一个变量。
[闭包示例3]:通过把 this 保存给 that,在函数中访问 that,从而创建了一个闭包
var prison = {
names: 'Mike Mikowski and Josh Powell',
who: function () {
var that = this;
$.ajax({
success: function () {
console.log( that.names );
}
});
}
};
/**
* 尽管在 Ajax 请求返回的时候,who() 已经执行完毕,但是 that 变量不会被垃圾回收
* 在 success 方法中可以使用该变量
*/
prison.who(); // "Mike Mikowski and Josh Powell"
2. 闭包是如何工作的:
参看[闭包示例1],当调用 makePrison 时,为这次特定的调用创建了一个执行环境对象,将传入的值赋予 prisoner。
[闭包示例4]:
var curryLog, logHello, logGoodbye;
curryLog = function ( arg_text ) {
var log_it = function () { console.log( arg_text ); }
return log_it;
};
logHello = curryLog('hello');
logGoodbye = curryLog('goodbye');
curryLog('fred')(); // 'fred'
logHello(); // 'hello'
logGoodbye(); // 'goodbye'
delete window.logGoodbye; // 通过 var 声明的变量是不能通过 delete 操作符来删除的
logGoodbye(); // 'goodbye'
注意:
1)每次调用函数时都会创建一个唯一的执行环境对象。
2)执行环境对象是 JavaScript 引擎的一部分,在 JavaScript 中不能直接访问。
3)函数执行完后,执行对象就会被丢弃,除非调用者引用了它。
4)如果函数返回的是数字,就不能引用函数的执行环境对象。但是,如果函数返回的是一个更复杂的结构,像是函数、对象或者数组,将返回值保存到一个变量上(有时是误用),就创建了一个对执行环境的引用。
[闭包示例5]:
var menu, outer_function,
food = 'cake';
outer_function = function () {
var fruit, inner_function;
fruit = 'apple';
inner_function = function () {
return {
food: food,
fruit: fruit
};
}
return inner_function;
};
menu = outer_function();
menu(); // {food: "cake", fruit: "apple"}
解释说明:
当调用
outer_function
时,创建了一个执行环境。在这个执行环境中定义了
inner_function
,因为在outer_function
执行环境里面定义了inenr_function
,它有权限访问outer_function
作用域内的所有变量,这里是food
、fruit
、outer_function
、inner_function
和menu
。当
outer_function
执行完时,你可能期望在执行环境中的所有东西都会被垃圾回收期销毁。——>错!然而,因为
inner_function
的引用保存给了全局作用域中的变量menu
,所以它并不会被销毁。在声明
inner_function
的作用域内,需要保留对所有变量的访问权限,它“关闭”了outer_function
执行环境的大门,阻止垃圾回收器来移除它们。这就是闭包!!!!!
[闭包示例6]:
function sendAjaxRequest() {
var scoped_var = 'yay';
$.ajax({
success: function () {
console.log(scoped_var);
}
});
}
sendAjaxRequest(); // 当 Ajax 请求成功完成时,输出 'yay'
问:为什么在 Ajax 请求返回后,scope_var 还是可以访问的?
答:因为 success
方法是在调用 sendAjaxRequest
时创建的执行环境中定义的,此时 scoped_var
在作用域中。
《单页Web应用--温故JavaScrpt》学习笔记整理的更多相关文章
- 《MySQL必知必会》学习笔记整理
简介 此笔记只包含<MySQL必知必会>中部分章节的整理笔记.这部分章节主要是一些在<SQL必知必会>中并未讲解的独属于 MySQL 数据库的一些特性,如正则表达式.全文本搜索 ...
- MySQL必知必会(第4版)整理笔记
参考书籍: BookName:<SQL必知必会(第4版)> BookName:<Mysql必知必会(第4版)> Author: Ben Forta 说明:本书学习笔记 1.了解 ...
- 《SQL必知必会》学习笔记整理
简介 本笔记目前已包含 <SQL必知必会>中的所有章节. 我在整理笔记时所考虑的是:在笔记记完后,当我需要查找某个知识点时,不需要到书中去找,只需查看笔记即可找到相关知识点.因此在整理笔记 ...
- 《MySQL必知必会》学习笔记——前言
前言 MySQL已经成为世界上最受欢迎的数据库管理系统之一.无论是用在小型开发项目上,还是用来构建那些声名显赫的网站,MySQL都证明了自己是个稳定.可靠.快速.可信的系统,足以胜任任何数据存储业务的 ...
- 《mysql必知必会》读书笔记--存储过程的使用
以前对mysql的认识与应用只是停留在增删改查的阶段,最近正好在学习mysql相关内容,看了一本书叫做<MySQL必知必会>,看了之后对MySQL的高级用法有了一定的了解.以下内容只当读书 ...
- MySQL必知必会1-20章读书笔记
MySQL备忘 目录 目录 使用MySQL 检索数据 排序检索数据 过滤数据 数据过滤 用通配符进行过滤 用正则表达式进行搜索 创建计算字段 使用数据处理函数 数值处理函数 汇总数据 分组数据 使用子 ...
- 《SQL必知必会》学习笔记二)
<SQL必知必会>学习笔记(二) 咱们接着上一篇的内容继续.这一篇主要回顾子查询,联合查询,复制表这三类内容. 上一部分基本上都是简单的Select查询,即从单个数据库表中检索数据的单条语 ...
- 《MySQL必知必会》整理
目录 第1章 了解数据库 1.1 数据库基础 1.1.1 什么是数据库 1.1.2 表 1.1.3 列和数据类型 1.1.4 行 1.1.5 主键 1.2 什么是SQL 第2章 MySQL简介 2.1 ...
- MySQL必知必会复习笔记(1)
MySQL必知必会笔记(一) MySQL必知必会是一本很优秀的MySQL教程书,并且相当精简,在日常中甚至能当成一本工作手册来查看.本系列笔记记录的是:1.自己记得不够牢的代码:2.自己觉得很重要的代 ...
- mysql学习--mysql必知必会1
例如以下为mysql必知必会第九章開始: 正則表達式用于匹配特殊的字符集合.mysql通过where子句对正則表達式提供初步的支持. keywordregexp用来表示后面跟的东西作为正則表達式 ...
随机推荐
- MySQL数据库百万级高并发网站实战
在一开始接触PHP接触MYSQL的时候就听不少人说:“MySQL就跑跑一天几十万IP的小站还可以,要是几百万IP就不行了”,原话不记得了,大体 就是这个意思.一直也没有好的机会去验证这个说法,一是从没 ...
- 快排找第k大模板
int get_kth(int l,int r) { if (l==r) return a[r]; ]; while (i<j) { while (a[i]<mid) i++; while ...
- Android Phonebook编写联系人UI加载及联系人保存流程(三)
2014-01-07 09:54:13 将百度空间里的东西移过来. 本文从点击“添加联系人”Button开始,分析新建联系人页面UI是如何加载,以及新的联系人信息是如何保存的,借此,我们一探Phon ...
- 算法导论-钢条切割 C# 递归实现
下班前看到有位兄弟写 钢条切割问题,尝试实现C#版, 还没有实现最优版,分享一下. int[] parr; private void button1_Click(object sender, Even ...
- Hello Struts2
Struts2 概述 Struts2 是一个用来开发 MVC 应用程序的框架. 它提供了 Web 应用程序开发过程中的一些常见问题的解决方案: 对来自用户的输入数据进行合法性验证; 统一的布局; 可扩 ...
- C++-标准输入输出
1,cout 1) 用来向标准输出打印. 2) 如果参数是char*类型,则直接输出字符串.如果想要输出地址,则需要强制转换: <<static_cast<void*>(con ...
- 1、ViewModel类的构建和INoyifyPropertyChanged的应用
public class SampleItem : INotifyPropertyChanged { public SampleItem() { } private string title; pub ...
- Hibernate中的一级缓存、二级缓存和懒加载
1.为什么使用缓存 hibernate使用缓存减少对数据库的访问次数,从而提升hibernate的执行效率.hibernate中有两种类型的缓存:一级缓存和二级缓存. 2.一级缓存 Hibenate中 ...
- [USACO精选] 第一章 数值计算
好不容易坑来了传说中的USACO精选,近100题我要是能做完就哈哈哈哈了…继今天学并查集连番受挫之后,决定写一写基础题. #0 负二进制 2014-01-10 其实是想到就会做,不想到就不会做的题,数 ...
- win10 用微软账户登录无法访问共享的问题
百度找了一大堆可以解决的,最终最简单的方式(可能是bug): 测试了一下,Win10用微软账户登录的,连局域网共享时,输入用户名的时候,前面加个乱七八糟的域名就可以访问了: 比如: 用户名: ba ...