基础

提升(Hoisting)

  1. 简介

In JavaScript, functions and variables are hoisted. Hoisting is JavaScript's behavior of moving declarations to the top of a scope(the global scope or the current function scope).

That means that you are able to use a function or a variable before it has been declared, or in other words: a function or variable can be declared after it has been used already.

  1. 示例
    2.1 变量提升
foo = 2;
var foo;
// is implicitly understood as:
var foo;
foo = 2;

2.2 函数提升

hoisted(); //logs "foo"
function hoisted() {
      console.log("foo");
}

作用域和作用域链(Scope and Scope Train)

  1. 作用域
    任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域局部作用域两种。
  • 全局作用域
    在代码中任何地方都能访问到的对象拥有全局作用域。一般来说以下几种情形拥有全局作用域。
    (1)最外层函数和在最外层函数外定义的变量拥有全局作用域。
    (2)所有未声明直接赋值的变量拥有全局作用域。
    (3)所有window对象的属性拥有全局作用域。
    全局变量都是window对象的属性。

  • 局部作用域
    和全局作用域相反,拥有局部作用域的变量或函数只在固定的代码片段之内可以被访问到。

  1. 作用域链
    在JavaScript中,函数也是对象。函数对象和其他对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。

函数的作用域链是由一系列的对象(函数的活动对象+0个到多个的上层函数的活动对象+最后的全局对象)组成的。在函数执行的时候,会按照先后顺序从这些对象的属性中寻找函数体中用到的标识符的值。函数会在定义时将它们各自所处环境(全局上下文或者函数上下文)的作用域链存储到自身的[[scope]]内部属性中。

全局对象:JavaScript引擎在脚本开始执行之前就会创建全局对象,并添加一些预定义的属性。在脚本中定义的全局变量也会成为全局对象的属性。

活动对象:当JavaScript引擎调用函数时,被调用的函数会创建一个新的活动对象。所有在函数内部定义的局部变量、传入函数的命名参数和arguments对象都会作为这个活动对象的属性。这个活动对象加上该函数的[[scope]]内部属性中存储的作用域链就构成了本次函数调用的作用域链。

全局函数作用域链

var x = 10;
var y = 0;

function testFn(i){
      var x = true;
      y = y + 1;
      alert(i);  
}
testFn(10);

执行 testFn(10) 时的作用域链

内部函数作用域链

function outerFn(i, j) {
    var x = i + j;
    return innerFn(x) {
        return i + x;
    }
}

var func1 = outerFn(5, 6);
var func2 = outerFn(10, 20);
alert(func1(10)); //返回15
alert(func2(10)); //返回20

执行函数func1(10)时的作用域链

执行函数func2(10)时的作用域链

问题:一个活动对象在函数执行时被创建,但在函数执行完毕后会不会被销毁?

  • 没有内部函数的函数
 function outerFn(x) {
     return x * x;
 }
 var y = outerFn(2);

如果函数没有内部函数,则在该函数执行时,当前活动对象会被添加到该函数的作用域链的最前端.作用域链是唯一引用这个活动对象的地方.当函数退出时,活动对象会被从作用域链上删除,由于再没有任何地方引用这个活动对象,则它随后会被垃圾回收器销毁.

  • 包含内部函数的函数,但这个内部函数没有被外部函数之外的变量引用
  function outerFn(x) {
      
      //在outerFn外部没有指向square的引用
      function square(x) {
          return x * x;
      }
      
      //在outerFn外部没有指向cube的引用
      function cube(x) {
          return x * x * x;
      }

      var temp = square(x);
      return temp / 2;
  }
  var y = outerFn(5);

在这种情况下,函数执行时创建的活动对象不仅添加到了当前函数的作用域链的前端,而且还添加到了内部函数的作用域链中.当该函数退出时,活动对象会从当前函数的作用域链中删除,活动对象和内部函数互相引用着对方,outerFn函数的活动对象引用着嵌套的函数对象square和cube,内部函数对象square和cube的作用域链中引用了outerFn函数的活动对象.但由于它们都没有外部引用,所以都将会被垃圾回收器回收.

  • 包含内部函数的函数,但外部函数之外存在指向这个内部函数的引用
    例子1:
  function outerFn(x) {
      //内部函数作为外部函数的返回值被引用到了外部
      return innerFn() {
          return x * x;
      }
  }
  //引用着返回的内部函数
  var square = outerFn(5);
  square();

例子2:

 var square;
 function outerFn(x) {
     //通过全局变量引用到了内部函数
     square = function innerFn() {
         return x * x;
     }
 }
 outerFn(5);
 square();

在这种情况下,outerFn函数执行时创建的活动对象不仅添加到了当前函数的作用域链的前端,而且还添加到了内部函数innerFn的作用域链中(innerFn的[[scope]]内部属性).当外部函数outerFn退出时,虽然它的活动对象从当前作用域链中删除了,但内部函数innerFn的作用域链仍然引用着它. 由于内部函数innerFn存在一个外部引用square,且内部函数innerFn的作用域链仍然引用着外部函数outerFn的活动对象,所以在调用innerFn时,仍然可以访问到outerFn的活动对象上存储着的变量x的值。

  1. 作用域链和代码优化
  • 从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。因为全局变量总是存在运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候,应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量再使用。
    例如:
function changeColor() {
    document.getElementById("btnChange").onclick = function() {
       document.getElementById("targetCanvas").style.backgroundColor = "red";
    }
}

改成:

function changeColor() {
    var doc = document;
    doc.getElementById("btnChange").onclick = function() {
        doc.getElementById("targetCanvas").style.backgroundColor = "red";
    } 
}
  • 避免使用with语句,因为with语句会改变作用域链,会将with后面的对象推到作用域链的顶端,意味着函数所有的局部变量都处在第二个作用域链对象中了,造成了访问的代价。
  • catch语句也会改变作用域链,代码转入catch语句的时候,会把异常对象推到函数作用域链的顶端。因此,应该避免在异常处理环节访问局部变量。

分号自动添加机制(ASI)

  1. 不用使用分号结尾的语句
  • for循环和while循环(do...while循环是有分号的)
for(;;;) {}

while(true) {}

do{
    a--;
}wile(a > 0);
  • 分支语句:if,switch,try
if(true) {}
switch(a) {
    case 0:break;
    default:;
}
  • 函数的声明语句(但函数表达式还是要加分号的)
function func(x) {return x;}

var f = function f(x) {return x};
  1. 分号的自动添加
  • 除了本来就不用写分号的情况,JavaScript引擎还有一个特点,就是在应该写分号却没有写的情况下,它会自动添加(Automatic Semiconlon Insertion)。
    但是,这种添加也不是绝对的。如果下一行的开始与本行的结尾可以连在一起解析,就不会自动添加分号。
  • 一般来说,如果下一行起首的是(, [, +, -, /这五个字符中的一个,分号不会被自动添加。
  • 另外,如果一行的起首是++或--运算符,则它们的前面会自动添加分号。
  • 如果continue、break、return和throw这四个语句后面,直接跟换行符,则会自动添加分号。

数据类型(Data Type)

在 ECMAScript 中,变量可以存在两种类型的值,即原始值引用值
原始值
存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
引用值
存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存处。
ECMA-262把术语类型(type)定义为值的一个集合,每一种原始类型定义了它包含的值的范围及其字面量的表示形式。
ECMAScript有5种原始类型(primitive type),即Undefined, Null, Boolean, Number和String。
在许多语言中,字符串都被看作引用类型,而非原始类型,因为字符串的长度是可变的。ECMAScript 打破了这一传统。
ECMAScript 提供了 typeof 运算符来判断一个值是否在某种类型的范围内。可以用这种运算符判断一个值是否表示一种原始类型:如果它是原始类型,还可以判断它表示哪种原始类型。

函数(Function)

  • 函数也是一种对象,函数名是变量,变量的值即是函数对象在堆内存中的地址。所有函数都应看作Function类的实例。
  • 闭包:闭包指的是一种函数,这种函数能够引用函数外定义的变量。Javascript闭包函数的基础就是前面提到的函数作用域链。使用闭包的目的是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存的使用量,使用不当很容易造成内存泄漏。

对象(Object)

  • 面向对象的原则和要求:
  1. 封装 - 把相关的信息(无论数据或方法)存储在对象中的能力
  2. 聚合 - 把一个对象存储在另一个对象内的能力
  3. 继承 - 由另一个类(或多个类)得来类的属性和方法的能力
  4. 多态 - 编写能以多种方法运行的函数或方法的能力
  • ECMAScript只有公有作用域,没有私有作用域和受保护作用域,也没有静态作用域,但是可以给构造函数提供属性和方法。

  • this指针:this总是指向调用该函数的对象。使用this指针是因为实例化对象时无法确定使用的变量名,所以用this可以保证方法的可重用性。

  • 创建对象实例的方式

  1. 工厂方法
  2. 构造函数方式
  3. 原型方式
  4. 混合的构造函数/原型方式
  5. 动态原型方法
  6. 混合工厂方式
  • 继承机制的实现
  1. 对象冒充
  2. call方法
  3. apply方法
  4. 原型链
  5. 混合方式
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容