Javascript 严格模式详解

原文链接地址
作者: 阮一峰

一、概述

除了正常运行模式,ECMAscript 5添加了第二种运行模式:”严格模式”(strict mode)。顾名思义,这种模式使得Javascript在更严格的条件下运行。

设立”严格模式”的目的,主要有以下几个:
  - 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
  - 消除代码运行的一些不安全之处,保证代码运行的安全;
  - 提高编译器效率,增加运行速度;
  - 为未来新版本的Javascript做好铺垫。
“严格模式”体现了Javascript更合理、更安全、更严谨的发展方向,包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。
另一方面,同样的代码,在”严格模式”中,可能会有不一样的运行结果;一些在”正常模式”下可以运行的语句,在”严格模式”下将不能运行。掌握这些内容,有助于更细致深入地理解Javascript,让你变成一个更好的程序员。

本文将对”严格模式”做详细介绍。

二、进入标志

进入”严格模式”的标志,是下面这行语句:
  “use strict”;
老版本的浏览器会把它当作一行普通字符串,加以忽略。
三、如何调用
“严格模式”有两种调用方法,适用于不同的场合。
3.1 针对整个脚本文件
将”use strict”放在脚本文件的第一行,则整个脚本都将以”严格模式”运行。如果这行语句不在第一行,则无效,整个脚本以”正常模式”运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。
(严格地说,只要前面不是产生实际运行结果的语句,”use strict”可以不在第一行,比如直接跟在一个空的分号后面。)
  
  
上面的代码表示,一个网页中依次有两段Javascript代码。前一个script标签是严格模式,后一个不是。
3.2 针对单个函数
将”use strict”放在函数体的第一行,则整个函数以”严格模式”运行。
  function strict(){
    “use strict”;
    return “这是严格模式。”;
  }
  function notStrict() {
    return “这是正常模式。”;
  }
3.3 脚本文件的变通写法
因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。
  (function (){

    “use strict”;
    // some code here

   })();

四、语法和行为改变

严格模式对Javascript的语法和行为,都做了一些改变。
4.1 全局变量显式声明
在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。
“use strict”;
  v = 1; // 报错,v未声明
  for(i = 0; i < 2; i++) { // 报错,i未声明
  }
因此,严格模式下,变量都必须先用var命令声明,然后再使用。
4.2 静态绑定
Javascript语言的一个特点,就是允许”动态绑定”,即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时(runtime)确定的。
严格模式对动态绑定做了一些限制。某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,在编译阶段就确定。这样做有利于编译效率的提高,也使得代码更容易阅读,更少出现意外。
具体来说,涉及以下几个方面。
(1)禁止使用with语句
因为with语句无法在编译时就确定,属性到底归属哪个对象。
  “use strict”;
  var v = 1;
  with (o){ // 语法错误
    v = 2;
  }
(2)创设eval作用域
正常模式下,Javascript语言有两种变量作用域(scope):全局作用域和函数作用域。严格模式创设了第三种作用域:eval作用域。
正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。
  “use strict”;
  var x = 2;
  console.info(eval(“var x = 5; x”)); // 5
  console.info(x); // 2
4.3 增强的安全措施
(1)禁止this关键字指向全局对象
  function f(){
    return !this;
  }
  // 返回false,因为”this”指向全局对象,”!this”就是false
  function f(){
    “use strict”;
    return !this;
  }
  // 返回true,因为严格模式下,this的值为undefined,所以”!this”为true。
因此,使用构造函数时,如果忘了加new,this不再指向全局对象,而是报错。
  function f(){
    “use strict”;
    this.a = 1;
  };
  f();// 报错,this未定义
(2)禁止在函数内部遍历调用栈
  function f1(){
    “use strict”;
    f1.caller; // 报错
    f1.arguments; // 报错
  }
  f1();
4.4 禁止删除变量
严格模式下无法删除变量。只有configurable设置为true的对象属性,才能被删除。
  “use strict”;
  var x;
  delete x; // 语法错误
  var o = Object.create(null, {‘x’: {
      value: 1,
      configurable: true
  }});
  delete o.x; // 删除成功
4.5 显式报错
正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。
  “use strict”;

  var o = {};
  Object.defineProperty(o, “v”, { value: 1, writable: false });
  o.v = 2; // 报错
严格模式下,对一个使用getter方法读取的属性进行赋值,会报错。
  “use strict”;

  var o = {

    get v() { return 1; }
  };
  o.v = 2; // 报错
严格模式下,对禁止扩展的对象添加新属性,会报错。
  “use strict”;
  var o = {};
  Object.preventExtensions(o);
  o.v = 1; // 报错
严格模式下,删除一个不可删除的属性,会报错。
  “use strict”;
  delete Object.prototype; // 报错
4.6 重名错误
严格模式新增了一些语法错误。
(1)对象不能有重名的属性
正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。
  “use strict”;
  var o = {
    p: 1,
    p: 2
  }; // 语法错误
(2)函数不能有重名的参数
正常模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,这属于语法错误。
  “use strict”;
  function f(a, a, b) { // 语法错误
    return ;
  }
4.7 禁止八进制表示法
正常模式下,整数的第一位如果是0,表示这是八进制数,比如0100等于十进制的64。严格模式禁止这种表示法,整数第一位为0,将报错。
  “use strict”;
  var n = 0100; // 语法错误
4.8 arguments对象的限制
arguments是函数的参数对象,严格模式对它的使用做了限制。
(1)不允许对arguments赋值
  “use strict”;
  arguments++; // 语法错误
  var obj = { set p(arguments) { } }; // 语法错误
  try { } catch (arguments) { } // 语法错误
  function arguments() { } // 语法错误
  var f = new Function(“arguments”, “‘use strict’; return 17;”); // 语法错误
(2)arguments不再追踪参数的变化
  function f(a) {
    a = 2;
    return [a, arguments[0]];
  }
  f(1); // 正常模式为[2,2]
  function f(a) {
    “use strict”;
    a = 2;
    return [a, arguments[0]];
  }
  f(1); // 严格模式为[2,1]
(3)禁止使用arguments.callee
这意味着,你无法在匿名函数内部调用自身了。
  “use strict”;
  var f = function() { return arguments.callee; };
  f(); // 报错
4.9 函数必须声明在顶层
将来Javascript的新版本会引入”块级作用域”。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。
  “use strict”;
  if (true) {
    function f() { } // 语法错误
  }
  for (var i = 0; i < 5; i++) {
    function f2() { } // 语法错误
  }
4.10 保留字
为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。
使用这些词作为变量名将会报错。
  function package(protected) { // 语法错误
    “use strict”;
    var implements; // 语法错误
  }
此外,ECMAscript第五版本身还规定了另一些保留字(class, enum, export, extends, import, super),以及各大浏览器自行增加的const保留字,也是不能作为变量名的。

五、参考链接

js声明函数的方式

声明函数

ECMAScript规定了三种声明函数方式

构造函数

  • 首先函数也是对象的一种,我们可以通过其构造函数,使用new来创建一个函数对象
    • var printName = new Function(“console.log(‘Byron’);”);
      这种创建对象的方式按理来说应该是最正规的方式,但是因为性能原因极少使用

函数声明

1
2
3
function functionName(){
statement;
}
  • 使用function关键字可以声明一个函数,看个例子
    1
    2
    3
    function printName(){
    console.log('Byron');
    }

这里不是一个语句所以不用加分号(也可以加),只是一个声明。
printName();

函数表达式

1
2
3
var printName = function(){
console.log('Byron');
};

在此处只是将函数赋给printName,执行的时候写printName();即可执行。
如果改成

1
2
3
var printName = function(){
console.log('Byron');
}();

在后面加(),则表示函数执行后赋值给printName。本函数没有返回值所以printName并没有得到任何值。

JS CSS 渲染机制

JS CSS 渲染机制

  • 解析 HTML 标签, 构建 DOM 树
  • 解析 CSS 标签, 构建 CSSOM 树
  • 把 DOM 和 CSSOM 组合成 渲染树 (render tree)
  • 在渲染树的基础上进行布局, 计算每个节点的几何结构
  • 把每个节点绘制到屏幕上 (painting)

详细浏览器的工作原理,可参考How browsers work

js变量提升

变量提升

JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升

我们写了一个赋值语句

var a = 2;

实际上执行过程是解释器在未执行的时候先解析出变量声明,然后给他初始值undefined,然后才逐句执行程序

var a;
a = 2;

这样看起来没什么区别,但是在多语句的情况下会有差别,我们知道一个变量如果不存在我们就使用会报错

console.log(xxx); // Uncaught ReferenceError: xxx is not defined
我们在使用一个变量之前必须声明变量,但是由于变量提升,我们如果声明了变量,即使在声明语句前使用也是可以的,只不过其值是初始值undefined

console.log(xxx); // undefined
var xxx = 2;

js变量及对象

JavaScript是弱类型语言,但不是没有数据类型,ECMAScript规定了5种简单的数据类型

Null
Undefined
Boolean
Number
String

还有一种复杂的数据类型——Object,Object本质是一组无序的名值对组合,ECMAScript不支持自定义数据类型,所以任何值最终都是上述六种类型之

  • 对象(object)是JavaScript的核心概念,也是最重要的数据类型。JavaScript的所有数据都可以被视为对象,这也是我们常说的一切皆为对象。

  • 简单说,所谓对象,就是一种无序的数据集合,由若干个“键值对”(key-value)构成。key我们称为对象的属性,value可以是任何JavaScript类型,甚至可以是对象

var o = new Object();
o.name = ‘Byron’;
o.age = 24;

  • 我们也可以通过字面量的方式定义对象,实际上这种方法更常用

var o = {
name: ‘Byron’,
age:24
}

  • key可以使用引号包裹,也可以直接写,对象有一个默认的方法toString,用来返回对象的字符串表示

console.log(o); // Object {name: “Byron”, age: 24}
console.log(o.toString()); // [object Object]
object的属性读取有两种方式

o.name;
o[‘name’];

设置placeholder文本的样式

CSS设置placeholder文本的样式

placeholder是HTML5 input的新属性,英文意思是占位符,它一般表示input输入框的默认提示值。

当然我们今天讨论的不是placeholder这个属性,而是设置placeholder的文本样式的选择器。

火狐和webkit核心浏览器的属性名字不一样,谁叫不是一个娘生的呢。

/ webkit 浏览器/

1
2
3
4
5
6
7
8
9
10
11
12
#field1::-webkit-input-placeholder { color:#00f; }
#field2::-webkit-input-placeholder {
color:#090;
background:lightgreen;
text-transform:uppercase;
}
#field3::-webkit-input-placeholder {
font-style:italic;
text-decoration:overline;
letter-spacing:3px;
color:#999;
}

/ 火狐浏览器 /

1
2
3
4
5
6
7
8
9
10
11
12
#field1::-moz-placeholder { color:#00f; }
#field2::-moz-placeholder {
color:#090;
background:lightgreen;
text-transform:uppercase;
}
#field3::-moz-placeholder {
font-style:italic;
text-decoration:overline;
letter-spacing:3px;
color:#999;
}

当然,和CSS设置::selection选中文本样式一样,你只能设置几个和文本有关系的CSS样式:Color, font-style, font-variant, background, text-decoration
实例demo

PS:IE从IE 10才开始支持placeholder属性,伪选择器名字还不得而知,估计是加前缀-ms-了

js语法

  • Javascript变量使用var定义。
    1
    2
    3
    4
    var a = 1;
    var b = "hello",
    c = 'world';
    var d = [];

javascript是弱类型语言,在变量定义时没有那么严格,var可定于任何类型的变量。

语句以分号结尾.
变量名以$、字母、_开头,其他字符可以是数字,区分大小写。

  • 注释方法

    1
    2
    3
    4
    5
    6
    /* 
    var a = 1;
    var b = 2;
    用单斜线加星号多行注释
    */
    var c = 3; //用双斜线单行注释
  • 标识符是指变量、函数、属性的名字,或者函数的参数。标识符的书写有几个特征

    区分大小写
    第一个字符必须是字母、下划线(_)、或者是$
    后面的可以是字母、数字、下划线、$

  • 弱类型语言

  • 在一些编译语言(C、Java、C#)等变量的类型是固定的,在声明变量的时候就要标识其类型,在程序执行前编译阶段变量的类型就确定了,而且不能改变,我们称之为强类型

int a = 2;
string b = “hello”;

一些常见的解释型语言(PHP、JavaScript)等变量的类型是松散的,一个变量可以用来保存任何类型的数据,变量的类型不是由声明时决定(声明的时候只是用了var运算符),而是在程序执行阶段由其值的类型决定,随着程序运行其值类型改变,变量类型也会发生改变

var message = 1; //message 类型就是数字
message = “hello world!”; //message 类型变为字符串

需要强调的一点:弱类型语言不是没有数据类型!

js的异步加载

  • javascript脚本的加载执行分为三种情况,可以通过在引用标签< scripe>内设定属性值,规定脚本的加载方式

  • < script src=”script.js”>

没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。

  • < script async src=”script.js”>

有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步),script的加载和执行都是异步的。

  • < script defer src=”script.js”>

有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。

defer:脚本延迟到文档解析和显示后执行,有顺序,只有 Internet Explorer 支持 defer 属性。
async:规定一旦脚本可用,则会异步执行,仅适用于外部脚本,是html5的属性。不保证顺序。

js和css放置顺序产生的问题

CSS 和 JS 放置顺序不同,对于不同浏览器,由于解析机制不同,在解析时会产生一些问题,所以一般建议

使用 link 标签将样式表放在顶部

  • 白屏问题

如果把样式放在底部,对于IE浏览器,在某些场景下(新窗口打开,刷新等)页面会出现白屏,而不是内容逐步展现。

也就是在浏览器解析时,浏览器会加载网页内容,依次执行在加载样式、js,在渲染完成前的等待过程中,浏览器会出现白屏没有任何内容的问题。

如果使用 @import 标签,即使 CSS 放入 link, 并且放在头部,也可能出现白屏

  • FOUC (Flash of Unstyled Content) 无样式内容闪烁

如果把样式放在底部,对于IE浏览器,在某些场景下(点击链接,输入URL,使用书签进入等),会出现 FOUC 现象(逐步加载无样式的内容,等CSS加载后页面突然展现样式).对于 Firefox 会一直表现出 FOUC .

这种解析机制一般是指般加载边解析,比如html和css里面的样式不同,会产生先后解析出来的样式产生覆盖,像是闪烁一样。

  • css和javascript的加载不一样,同一个域名下可以同时加载2个甚至4个css(并行加载几个与http协议有关),js只能一个一个进行加载

将JS放在底部

脚本会阻塞后面内容的呈现
脚本会阻塞其后组件的下载

对于图片和CSS, 在加载时会并发加载(如一个域名下同时加载两个文件). 但在加载 JavaScript 时,会禁用并发,并且阻止其他内容的下载. 所以把 JavaScript 放入页面顶部也会导致 白屏 现象.

js基础

JavaScript一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,是一种解释型语言。

对于javascript和java的关系,有一个人很形象的说法,“他们的区别就是雷锋和雷锋塔的区别”,也就是他们之间没有关系(java是一种编译型语言),知识javascript在发展过程中借助了如日中天的java名字改成了javascript。

ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言,也就是javascript语言的标准化和核心,是一种纯计算机语言(定义变量,循环语句,运算操作等)。

正常的javascript包含ECMAScript(核心)、DOM(文档对象模型Document Object Model)操作、BOM(浏览器对象模型(BrowserObjectModel)效果这三个部分.

JScript是由微软公司开发的活动脚本语言,是微软对ECMAScript规范的实现

一个网页的组成就包含以下三个部分:内容、样式和结构的分离,是web编程中很重要的一个思想。

网页 = Html+CSS+JavaScript
Html: 网页元素内容
CSS: 控制网页样式
JavaScript:操作网页内容,实现功能或者效果

,