5.1 语言基础
~~~~~~~~ 数据类型 ~~~~~~~
function sayHi() {
console.log(name);
console.log(age);
var name = 'Lydia';
let age = 21;
}
sayHi()
以上代码的输出是什么?
var, let
输出:
undefined
ReferenceError: Cannot access 'age' before initialization
解析:
var 声明的变量会被预解析,
var name
会被提升到作用域的最顶部所以在第 2 行
console.log(name)
时,name 是存在的,但是没有被赋值所以第 2 行是输出
undefined
而 let 会有暂时性死区
所以在 let 声明变量之前,都无法使用该变量
var name = 'World!';
(function () {
if (typeof name === 'undefined') {
var name = 'Jack';
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
} })();
以上代码的输出是什么?
var
输出:'Goodbye Jack'
解析:用 var 定义的变量“都”会被提升至所有代码的最前面。所以以上代码,实际上类似:
var name = 'World!';
(function () {
var name;
if (typeof name === 'undefined') {
// var name = 'Jack';
name = 'Jack';
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
} })();
TODO:where 会提升到 where
(function(){
var x = y = 1;
})();
console.log(y);
console.log(x);
以上代码的输出是什么?
var
输出:
1
ReferenceError: x is not defined
解析:在作用域内,var
变量定义和函数定义会被先行提升。
所以,上面的代码,实际上是:
var y; // 而 y 则隐式成了个全局变量
(function(){
var x; // 只“变量提升”第一个变量
// var x = y = 1;
y = 1;
x = 1;
})();
console.log(y);
console.log(x);
考察点:var
变量提升、隐式定义全局变量
var a = 11;
function test2(){
this.a = 22;
let b = ()=>{
console.log(this.a)
}
b();
}
var x = new test2();
以上代码的输出是什么?
for(var i=0;i<5;i++) {
setTimeout(function(){
console.log(i);
},1000);
}
console.log(i)
以上代码的输出是什么?
写一个函数,第 n 秒打印 n
// 1. var + 闭包
for(var i=0;i<5;i++){
(function(i){
setTimeout(function(){
console.log(i)
}, 1000*i)
})(i)
}
// 2. let
for(let i=0;i<5;i++){
setTimeout(function(){
console.log(i)
},1000*i)
}
symbol
Symbol 是 ES6 的新增属性,代表用给定名称作为唯一标识,这种类型的值可以这样创建,let id=symbol('id')
symbl 确保唯一,即使采用相同的名称,也会产生不同的值,我们创建一个字段,仅为知道对应 symbol 的人能访问。
symbol 并不是 100% 隐藏:
有内置方法
Object.getOwnPropertySymbols(obj)
可以获得所有的 symbol。也有一个方法
Reflect.ownKeys(obj)
返回对象所有的键,包括 symbol
但大多数内置方法和语法结构,都遵循通用约定——它们是隐藏的。
var a = 111111111111111110000
var b = 1111
console.log(a + b)
以上代码的输出是什么?
Number 精度
输出:111111111111111110000
(和 a 的值相等)
解析:
对于整数部分,有效数字最多能存 16 位。
// 整数部分,a 的有效数字有 16 位
'111111111111111110000'.length // 21
a.toExponential() // 1.1111111111111111e+20
// 而当 a 参加+运算时,它是不安全的
Number.MAX_SAFE_INTEGER // 9007199254740991
a >= Number.MAX_SAFE_INTEGER // true
'9007199254740991'.length // 16
// a + b, 最后 4 位精度刚好被扔了,所以结果值还是 a
1.11111111111111110000e+20
0.00000000000000001111e+20
关于浮点数精度的详情可查阅:https://anjia1.gitbook.io/cs/floating-point/reading
[,,,].join(",")
以上代码的运行结果是什么?
对象字面量
结果:",,"
解析:JavaScript 在使用字面量创建数组时,如果末尾有个 ,
会被省略。
所以 [,,,]
实际上只有三个元素 [,,]
,值都为空即 undefined
。而当三个元素使用 join(,)
时,会有两个分隔符 ,
。
[,,,] // [empty × 3]
function sidEffecting(ary) {
ary[0] = ary[2]
}
function bar(a,b,c) {
c = 10
sidEffecting(arguments)
return a + b + c
}
console.log(bar(1,1,1))
以上代码的输出是什么?
函数参数 arguments
输出:21
解析:函数内部的 arguments 是个引用,且和变量之间存在着值联动。
arguments 是个类数组,有 length 属性的 Object
arguments 对象的内部属性是用形参创建的 getter 和 setter 方法,它们任意一个的改变都会影响到另一方
判断变量类型的方法
运算符
typeof
运算符
instanceof
属性
constructor
方法
toString()
typeof a
a instanceof A
a.constructor == A
Object.prototype.toString.call(a)
类型转换规则
x == y
的结果是 true 或者 false
若 Type(x) 和 Type(y) 相同,则
Type 是 Undefined,返回 true
Type 是 Null,返回 true
Type 是 Boolean,同为 true/false 返回 true,否则返回 false
Type 是 String,当是完全相同的字符序列时返回 true,否则返回 false
Type 是 Number,则
若有一个为 NaN,则返回 false
若是 +0 和 -0,则返回 true
若是相等数值,返回 true
否则返回 false
Type 是对象,若引用的是同一个对象则返回 true,否则返回 false
一个是 null,一个是 undefined
若 x 是 null,y 是 undefined,则返回 true
若 x 是 undefined,y 是 null,则返回 true
一个是 String,一个是 Number
若 Type(x) 是 Number,Type(y) 是 String,则返回 x == ToNumber(y) 的结果
若 Type(x) 是 String,Type(y) 是 Number,则返回 ToNumber(x) == y 的结果
其中一个是 Boolean
若 Type(x) 是 Boolean,返回 ToNumber(x) == y 的结果
若 Type(y) 是 Boolean,返回 x == ToNumber(y) 的结果
一个是 String 或 Number,一个是 Object
若 Type(x) 是 String 或 Number,Type(y) 是 Object,返回 x == ToPrimitive(y)
若 Type(x) 是 Object,Type(y) 是 String 或 Number,返回 ToPrimitive(x) == y
返回 false
相关内容
ToPrimitive()
若是转成 string,则先调 toString()
若是转成 number,则先调 valueOf()
ToNumber()
数据类型
在 JavaScript 中,基本数据类型 = 原始数据类型(值类型) + 对象(引用类型)
值类型:按值访问,可操作保存在变量中的实际值
引用类型
Object, Array, RegExp, Date, Function
值类型的包装器:String, Number, Boolean
内置对象:Global, Math
~~~~~~~~ 类型转换 ~~~~~~~
其它类型转换为 Number 的规则
原始类型
Undefined,
NaN
Null,
0
Boolean,
true
1,false
0String, '' 0, 非空非数值
NaN
, 非空数值Number
Number
BigInt
Symbol, 不能转, 会报错
Object
先转换成相应的原始类型值
ToPrimitive
会先调用valueOf()
再调用toString()
再按原始类型的规则转
// 原始类型
Number(undefined) // NaN
Number(null) // 0
Number(true) // 1
Number(false) // 0
Number('') // 0
Number('123') // 123
Number('hello') // NaN
Number(Symbol('1')) // TypeError: Cannot convert a Symbol value to a number
// object
Number([]) // 0
Number({}) // NaN
Number([1]) // 1
Number(['1']) // 1
Number([1,2,3]) // NaN
Number({name: 'hi'}) // NaN
Number(()=>{}) // NaN
var a = [0];
if ([0]) {
console.log(a == true);
} else {
console.log("hi");
}
以上代码的输出是什么?
boolean 和 ==
输出:false
解析:
第 2 行,转 boolean 类型:数组
[0]
不为空,所以值是 true第 3 行,运算符
==
:如果一个操作数是 boolean,则另一个操作值在比较之前先转换为 Number,而Number([0])
的值是 0,于是最终变成了0 == true
console.log(+true);
console.log(!'hi');
以上代码的输出是什么?
var a = [1, 2, 3]
var b = [1, 2, 3]
var c = [1, 2, 4]
console.log(a == b)
console.log(a === b)
console.log(a > c)
console.log(a < c)
以上代码的输出是什么?
数组比大小
输出:false, false, false, true
解析:在 JavaScript 中
Array 本质上是 Object(引用类型)
引用类型比相等,比的是地址,所以,
==
和===
都不相等
而 Array 的
>
和<
运算符,方式却类似于字符串,比较字典顺序所以,
a < c
是 true
~~~~~~~~~ 运算符 ~~~~~~~
Object.is()
和 ==
, ===
的区别
==
会进行隐式类型转换===
不进行类型转换,是严格相等Object.is()
是在===
的基础上特别处理了 NaN, -0, +0
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
最重要的是,Object.is()
是有其特殊用途的,而不应该理解为是更严格的相等。
var val = 'hi'
console.log('Value is ' + (val === 'hi') ? 'Something' : 'Nothing')
以上代码的输出是什么?
const person = {
firstName: 'Lydia',
lastname: 'Hallie',
pet: {
name: 'Mara',
breed: 'Dutch Tulip Hound'
},
getFullName() {
return `${this.firstName}/${this.lastName}`
}
}
console.log(person.pet?.name)
console.log(person.pet?.family?.name)
console.log(person.getFullName?.())
console.log(person.getLastName?.())
以上代码的输出是什么?
?.
输出:Mara undefined Lydia/Hallie undefined
解析:?.
可选操作链,ES2020 新特性。 当 ? 前面的属性不是 undefined 或 null 时才会继续执行后续代码,否则直接返回左侧结果。
~~~~~~~~~ 语句 ~~~~~~~~
function foo(){
alert(1)
}()
以上代码是 IIFE 吗?为什么?
不是 IIFE。而且代码运行时会报错:Uncaught SyntaxError: Unexpected token ')'
解析:以 function 关键字开头的语句,会被解析为函数声明,而函数声明是不允许直接运行的。只有当解析器把这句话解析为函数表达式时,才能直接运行。比如改成以运算符开头:
!function foo(){
alert(1)
}(); // true
!function foo(){
alert(2)
return 'hi'
}(); // false
(function foo(){
alert(3)
})(); // undefined, 最日常的写法
(function foo(){
alert(4)
}()); // undefined, 全部包裹也可以
任何消除函数声明和函数表达式之间歧义的方法,都可以被解析器正确识别。
var i = function(){return 10}(); // undefined
1 && function(){return true}(); // true
1, function(){alert(1)}(); // undefined
如果不在乎返回值,添加一元运算符都是有效的:
!function(){alert(1)}() // true
+function(){alert(1)}() // NaN
-function(){alert(1)}() // NaN
~function(){alert(1)}() // -1
...
void function(){alert(1)}() // undefined
new function(){alert(1)}() // Object {}
delete function(){alert(1)}() // true
基础概念:语句、表达式、表达式语句
let obj = {
name: "Lydia",
age: 21
}
for (let item in obj) {
console.log(item)
}
以上代码的输出是什么?
const myLife = ['吃', '喝', '玩', '乐'];
for (let item in myLife) {
console.log(item);
}
for (let item of myLife) {
console.log(item);
}
以上代码的输出是什么?
~~~~~~~~~ 方法 ~~~~~~~~
const arr = [1, 2, [3, 4, [5]]];
console.log(arr.flat(1));
以上代码的输出是什么?
Array.flat()
输出:[1, 2, 3, 4, [5]]
扁平化,会创建一个新的、被扁平化的数组。扁平化的深度,取决于参数。
Array.prototype.flat()
会创建一个 new array,用所有的 sub-array elements 递归地 concat 到一起。depth 由参数指定,默认是 1。
const arr1 = [0, 1, 2, [3, 4]];
arr1.flat(1); // [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
arr2.flat(1); // [0, 1, 2, [[3, 4]]]
arr2.flat(2); // [0, 1, 2, [3, 4]]
arr2.flat(3); // [0, 1, 2, 3, 4]
var ary = Array(3);
ary[0] = 2
ary.map(function(elem) { return '1'; });
以上代码的运行结果是什么?
[1, 2, 3, 4].reduce((x, y) => console.log(`x=${x} y=${y}`))
以上代码的输出是什么?
Array.reduce()
输出:
x=1 y=2
x=undefined y=3
x=undefined y=4
解析:最容易理解 reduce()
情况,是求返回数组中所有元素的总和。如下:
// 简写
[1, 2, 3, 4].reduce((x, y) => x + y)
// 增加 log
[1, 2, 3, 4].reduce((x, y) => {
console.log(x, y)
return x + y
})
而当回调函数中没有返回值时,则第一个参数为 undefined
const name = 'Lydia Hallie';
const age = 21;
console.log(Number.isNaN(name));
console.log(Number.isNaN(age));
console.log(isNaN(name));
console.log(isNaN(age));
以上代码的输出是什么?
isNaN()
和 Number.isNaN()
输出:false, false, true, false
解析:
isNaN()
会先将传入的参数转换为 Number 类型Number.isNaN()
是先判断值的类型是不是 Number,若不是则直接返回 false即只有当值是
NaN
时才会返回 true
实现深度拷贝
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
数组去重
ES6 Set 去重
Array.from(new Set(array))
indexOf 循环去重
Object 键值对去重:把数组的值存成 Object 的 key 值,比如 Object[value1] = true
获得对象上的属性
从 ES5 开始,有三种方法可以列出对象的属性
for(let i in obj)
访问对象及其原型链中所有可枚举的类型object.keys
返回一个数组,包括所有可枚举的属性名称object.getOwnPropertyNames
返回一个数组包含不可枚举的属性
~~~~~~~~ 原型和类 ~~~~~~~~
原型链的顶端是什么?
Object 的原型是什么?
Object 的原型的原型是什么?
在数组的原型链上,实现删除数组重复数据的方法
Function.prototype.a = 1;
Object.prototype.b = 2;
function A() {}
var a = new A();
console.log(a.a, a.b);
console.log(A.a, A.b);
以上代码的输出是什么?
Function._proto_(getPrototypeOf)
是什么?
获取一个对象的原型:
在 chrome 中,可以用
_proto_
在 ES6 中,可以用
Object.getPrototypeOf
Function.proto 是指 Function 继承自什么对象。
Function.__proto__ === Object.prototype // false
Function.__proto__ === Function.prototype // true
Function 的原型也是 Function
Hero("37er")
执行结果为Hi! This is 37er
Hero("37er").kill(1).recover(30)
执行结果为Hi! This is 37er Kill 1 bug Recover 30 bloods
Hero("37er").sleep(10).kill(2)
执行结果为Hi! This is 37er
等待 10s 后Kill 2 bugs
编写代码,满足以上条件
function Hero(name) {
let o = new Object()
o.name = name
console.log(`Hi! This is ${o.name}`)
o.kill = (num) => {
console.log(`Kill ${num} bug` + (num > 1 ? 's' : ''))
return o;
};
o.recover = function (num) {
console.log(`Recover ${num} blood` + (num > 1 ? 's' : ''))
return o;
}
o.sleep = function (s) {
// TODO. 不能直接连调
// 只能 Hero("37er").sleep(2).then(o => o.kill(2))
return new Promise((resolve) => {
setTimeout(() => {
console.log('sleep...')
resolve(o)
}, s * 1000)
})
}
return o;
}
继承的实现方式
原型链继承,将父类的实例作为子类的原型,他的特点是实例是子类的实例也是父 类的实例,父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实 现,缺点是来自原型对象的所有属性被所有实例共享,无法实现多继承,无法向父类构 造函数传参。
构造继承,使用父类的构造函数来增强子类实例,即复制父类的实例属性给子类, 构造继承可以向父类传递参数,可以实现多继承,通过 call 多个父类对象。但是构造继 承只能继承父类的实例属性和方法,不能继承原型属性和方法,无法实现函数服用,每 个子类都有父类实例函数的副本,影响性能
实例继承,为父类实例添加新特性,作为子类实例返回,实例继承的特点是不限制调用方法,不管是 new 子类还是子类返回的对象具有相同的效果,缺点是实例是父类的实例,不是子类的实例,不支持多继承
拷贝继承
特点:支持多继承
缺点:效率较低,内存占用高(因为要拷贝父类的属性),无法获取父类不可枚举的方法(不可枚举方法,不能使用 for in 访问到)
组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
~~~~~~~~~ 运行时 ~~~~~~~~
let obj1 = {
name: 'obj1_name',
print: function(){
return () => console.log(this.name)
}
}
let obj2 = { name: 'obj2_name' }
obj1.print()();
obj1.print().call(obj2);
obj1.print.call(obj2)();
以上代码的输出是什么?
bind,apply,call 的区别
改变函数内部 this 指针的指向函数。
通过 apply 和 call,改变函数的 this 指向:
它们两个函数的第一个参数都是一样的,表示要改变指向的那个对象
第二个参数,apply 是数组,而 call 则是 arg1,arg2...这种形式。
// 在另一个对象 A 上,调用 B 对象的方法
B.apply(A, arguments)
B.call(A, args1, args2)
通过 bind 改变 this 作用域会返回一个新的函数,这个函数不会马上执行。
bind 除了返回的是函数之外,其参数和 call 一样。
this 的指向有哪几种?
默认绑定:全局环境中,this 默认绑定到 window(非严格模式时)
隐式绑定:一般地,被直接对象所包含的函数调用时,也称为方法调用,this 隐式绑定到该直接对象
隐式丢失:隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到 window
显式绑定:通过 call(), apply(), bind() 方法把对象绑定到 this 上
new 绑定:如果函数或者方法调用之前带有关键字 new,它就构成构造函数调用。对于 this 绑定来说,称为 new 绑定
构造函数通常不使用 return 关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值
如果构造函数使用 return 语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果
如果构造函数显式地使用 return 语句返回一个对象,那么调用表达式的值就是这个对象
闭包
闭包就是能够读取其它函数内部变量的函数,或者子函数在外调用, 子函数所在的父函数的作用域不会被释放。
TODO.
闭包本身的含义?
在 JS 里,闭包的形式通常是xxx,因为它符合闭包的定义。
在其它语言里,闭包是否有不同的形式?
闭包的使用场景
setTimeout(function(){
console.log(1)
}, 0);
new Promise(function(resolve,reject){
console.log(2);
resolve();
}).then(function(){
console.log(3)
}).then(function(){
console.log(4)
});
// Node.js
process.nextTick(function(){
console.log(5)
});
console.log(6);
以上代码的输出是什么?
setTimeout() 的时间含义
setTimeout()
函数只是将事件插入了任务列表,必须等当前代码都执行完,主线程才会去执行它指定的回调函数,有可能要等很久,所以没办法保证回调函数一定会在 setTimeout 指定的时间内执行。
设置的那个时间 ≈ 插入队列的时间 + 等待的时间
如何实现 sleep()
promise
async
generate
while
// 1. promise
function sleep(ms){
var temple = new Promise((resolve)=>{
console.log(111);
setTimeout(resolve, ms) // 起码是异步的
});
return temple
}
sleep(500).then(function(){
console.log(222)
})
// 先输出了 111,延迟 500ms 后输出 222
// 2. async
function sleep(ms){
return new Promise((resolve)=>
setTimeout(resolve,ms)
);
}
async function test(){
var temple = await sleep(1000);
console.log(1111)
return temple
}
test();
// 延迟 1000ms 输出了 1111
// 3. generate
function* sleep(ms){
yield new Promise(function(resolve,reject){
console.log(111);
setTimeout(resolve,ms);
})
}
sleep(500).next().value.then(function(){console.log(2222)})
// 4. while
function sleep(ms){
var start=Date.now(), expire=start+ms;
while(Date.now()<expire);
console.log('1111');
return;
}
// sleep(1000) 休眠了 1000ms 之后输出了 1111
// 缺点:容易造成死循环(一直占着...主进程...也太不行了吧...)
// 那放在...web worker 里?有点杀鸡用牛刀...
实现一个简单的 promise
Promises/A+ https://promisesaplus.com/
实现一个完美符合Promise/A+规范的Promise https://github.com/forthealllight/blog/issues/4
垃圾回收机制
必要性:
由于字符串、对象和数组没有固定大小,所以只有当它们的大小已知时,才能进行动态的存储分配。
JavaScript 程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。
不像 C/C++,JS 自己有一套垃圾回收机制(Garbage Collection)。JavaScript 的解释器可以检测到什么时候不再使用一个对象了,一旦确定了一个对象是无用的,它就可以把它所占用的内存释放掉了。
var a = "hello world";
var b = "world";
var a = b;
//这时,会释放掉"hello world",释放内存以便再引用垃圾回收的方法:标记清除、计数引用。
1. 标记清除
这是最常见的垃圾回收方式。
当变量进入环境时,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占的内存,只要执行流程进入相应的环境就可能用到它们。
当离开环境时,就标记为离开环境。
垃圾回收器在运行的时候,会给存储在内存中的变量都加上标记(所有都加),然后去掉环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器完成了内存的清除工作,并回收它们所占用的内存。
2. 引用记数法
记录每个值被引用的次数。
当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为 1
相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次 数就减 1,
当这个值的引用次数为 0 的时候,说明没有办法再访问这个值了,因此就把 所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为 0 的这些值。
用引用计数法会存在内存泄露。比如:
function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}
// objA 和 objB 通过各自的属性相互引用,此时,两个对象的引用次数都为 2
// 在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用域,
// 函数执行完成之后,因为计数不为 0,这样的相互引用如果大量存在就会导致内存泄露
// 在 DOM 对象中,也容易存在这种问题
var element = document.getElementById(’‘);
var myObj = new Object();
myObj.element=element;
element.someObject=myObj;
// 这样就不会有垃圾回收的过程
~~~~~~~~~ 其它 ~~~~~~~~~
前端模块化(CommonJS,AMD,ES Modules)
将复杂的文件拆成一个个独立的模块(比如单个 JS 文件),这样有利于重用(复用性)和维护(版本迭代)。一个模块是能实现特定功能的文件,有了模块就可以方便的使用别人的代码,想要什么功能就能加载什么模块。
与此同时,也会引来模块之间相互依赖的问题,所以有了 commonJS 规范,AMD,CMD 规范等等,以及用于 JS 打包(编译等处理)的 工具 webpack。
CommonJS 是一种模块规范,最初被用在 Node.js,成为 Node.js 的模块规范
而浏览器端,在 ES6 之前,类似的模块规范有 AMD
ES6 Module 是在语言标准的层面上实现了模块功能。有望成为浏览器和服务器通用的模块解决方案
CommonJS vs AMD
CommonJS 开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域。
模块输出
modules.exports
模块加载
require()
引入模块
AMD,异步模块定义。
requireJS 实现了 AMD 规范,主要用于解决下述两个问题:
多个文件有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应的时间越长
// requireJS 定义了一个函数 define,它是全局变量
// 定义模块 define()
define(['dependency'], function(){
var name = 'Byron';
function printName(){ console.log(name);
}
return {
printName: printName
};
});
// 加载模块 require(),异步加载,这样不会阻塞浏览器
require(['myModule'], function (my){
my.printName();
}
// 当前面的模块加载成功,回调才会被执行。
CommonJS vs ES Module
使用上的差别,主要有:
模块输出:值的拷贝 vs 值的引用
时机:运行时加载 vs 编译时输出接口
形式:单个值导出 vs 可导出多个
动态语法可以写在判断里 vs 静态语法只能写在顶层
this 值:当前模块 vs
undefined
(默认是严格模式)
"use strict";
ES5 引入了严格模式,旨在让代码更合理、更安全。
它对 JavaScript 的使用添加了一些限制,比如:
禁止使用 with 语句
禁止 this 指向全局对象 (???)
对象的属性名不能重复
此外,针对严格模式,编译器也会有特定的优化,所以代码的运行速度也会快一些。
严格模式,还可以很好地向后兼容,为未来的 JavaScript 做好铺垫。
ES6,ES7 的新语法
变量的声明和定义
增加了 let、const 声明变量
它们是块级作用域的(ES5 只有全局作用域和函数作用域)
块级作用域的好处是不再需要立即执行的函数表达式,循环体中的闭包不再有问题
箭头函数
异步的 promise, generator, async/await
模块化,更方便地进行模块化编程
class,可以更好的面向对象编程
其它:
数据类型
新的原始类型 symbol
新的数据结构 set 和 map
对已有类型的对象包装器,新增了一些方法。比如
字符串:模板字符串
函数:默认参数
对象属性:?.
运算符
结构赋值
rest
JS 的语言特性
C++, Java, JavaScript 这三种语言的区别
Last updated