变量提升
"use strict"
a = 1
var a = 2
console.log(window.a) // 2
console.log(a) // 2
/*
var声明会把变量提升到当前作用域的顶部,所以a=1不会报错
全局作用域下使用var声明变量,该变量会变成window的一个属性
以上2点与是否在严格模式下无关
*/
变量在未定义前就使用,会形成暂时性性死区
function sum(a = b, b =1) {
console.info(a, b)
}
sum() // Cannot access 'b' before initialization
// 先执行a = b, 但是b还未定义不能使用
变量提升
var name = 'zs'
;(function() {
if(typeof name === 'undefined') {
var name = 'tom'
console.info('Goodbye ' + name)
} else {
console.info('hi ' + name)
}
})()
// 'Goodbye zs'
/*
变量提升,var声明变量时会把变量自动提升到当前作用域的顶部
*/
变量提升
var a = 1
function a(){}
console.log(a)
var b
function b(){}
console.log(b)
function b(){}
var b
console.log(b)
// 1 b函数本身 b函数本身
/*
函数声明和var声明,两者都会发生提升,但是函数会优先提升,如果变量和函数同名,变量的提升就会忽略
*/
变量提升
console.info(a)
var a = 1
var getA = function() {
a = 2
}
function getA() {
a = 3
}
console.info(a)
getA()
console.info(a)
// undefined 1 2
/*
变量提升将变量a 提升到顶部
打印变量a 为 undefined
函数声明和函数表达式的区别,函数声明会有提升作用,在代码执行前就把函数提升到顶部,在执行上下文中生成函数定义,第二个getA会提升到顶部,然后是var声明getA提升,因为getA函数已经被声明,所以不需要再声明。
变量a 赋值为 1
打印变量a 为 1
变量getA赋值 新函数 function() {a=2}
执行getA
打印变量a 为 2
*/
作用域链,连等操作符从右向左执行
let a = b =10
;(function () {
let a = b = 20
})()
console.info(a) // 10
console.info(b) // 20
// 连等操作是从右向左执行的,相当于 全局b = 10, 全局a = 全局b,b没有声明就赋值了,会创建一个全局变量。
// 函数内执行同外部一样,b没有声明,直接对b赋值,因为作用域链,会一层一层向上查找,找到全局b,所以全局b被修改成20
作用域链
var i = 1
function b() {
console.log(i)
}
function a() {
var i = 2
b()
}
a() // console.info(1)
/*
作用域是一套变量的查找规则,每个函数在执行时都会创建一个执行上下文,其中会关联一个变量对象,也就是它的作用域,上面保存着函数能访问的所有变量,另外上下文中的代码在执行时还会创建一个作用域链
如果某个标识符在当前作用域中没有找到,会沿着外层作用域链继续查找,直到最顶端的全局作用域,因为js是词法作用域,在写代码阶段就作用域已经确定,在函数定义的时候确定的,而不是执行的时候。
因为b函数是在全局作用域中定义的,虽然在a函数中调用,但是它只能访问到全局的作用域,访问不到a函数作用域。
*/
作用域链
var scope = 'global scope'
function a(){
function b(){
console.log(scope)
}
return b
var scope = 'local scope'
}
a()() // undefined
/*
变量scope会提升到函数作用域顶部,b的上层作用域是a函数,自身没有scope会去上层作用域查找
*/
对象通过指针引用,. 运算符优先级高,连等操作符从右向左执行
var a = {n: 1}
var b = a
a.x = a = {n:2}
console.info(a.x) // undefined
console.info(b.x) // {n: 2}
/*
对象是通过指针指向堆内存中的一个对象
a = {n: 1}, 变量a的指针指向 初始对象{n:1}
b = a, 变量b的指针也指向 初始对象{n:1}
因为 . 运算符级别高,先执行 a.x 初始对象{n: 1, x: undefined}
a = {n: 2}, 变量a的指针指向 新对象{n:2}
a.x = a, 因为a.x最早执行过,所以相当于 初始对象{n: 1, x: undefined} 的 x 指向 新对象
原理 ({n: 1, x: undefined}).x = b.x = a = {n: 2}
*/
- 数组遍历方法,不同ES版本,对undefined处理方法不同
var arr = [0, 1, 2]
arr[10] = 10
console.log(arr.filter(function (x) {
return x === undefined
}))
// 结果[]
/*
arr[10] = 10, 那么索引3-9位置上都是undefined,arr[3]打印也是undefined,
但是这里涉及ECMAScript版本不同对应方法行为不同的问题,ES6之前遍历方法都会跳过未赋值过的位置,也就是空位。但是ES6新增的for..of方法不会跳过
*/
+ 符号的原理
console.info(1 + NaN) // NaN
console.info(1 + '3') // '13'
console.info(1 + true) // 2 (true转成1)
console.info(1 + false) // 1 (false转成0)
console.info(1 + undefined) // NaN (undefined转成NaN)
console.info(1 + null) // 1 (null转成0)
console.info(1 + {}) // '1[object Object]'
console.info(1 + []) // 1 (数组转成0)
console.info({} + []) // '[object Object]'
/*
考察 + 号,左侧都有值的行为
1.如果一个操作数是字符串,那么把另一个操作数转成字符串执行连接
2.如果一个操作数是对象,那么调用对象的valueOf方法转成原始值,如果没有该方法或调用后是非原始值,则调用toString方法
3.其他情况下,两个操作符都会被转成数字执行加法操作
*/
对象的key如果是对象,会被转成[object Object]
var a = {}
b = {key: 'b'}
c = {key: 'c'}
a[b] = 123
a[c] = 456
console.info(a[b]) // 456
console.info(a[c]) // 456
console.info(a) // {[object Object]: 456}
/*
对象的key如果是对象,会转成[object Object]
要使用对象作为key,可以设置 map 类型数据
var a = new Map()
*/
this指向
var out = 25
var inner = {
out: 20,
func: function () {
var out = 30
return this.out
}
};
console.log((inner.func, inner.func)()) // 25
console.log(inner.func()) // 20
console.log((inner.func)()) // 20
console.log((inner.func = inner.func)()) // 25
/*
1逗号操作符会返回表达式中的最后一个值,这里为inner.func对应的函数,注意是函数本身,然后执行该函数,该函数并不是通过对象的方法调用,而是在全局环境下调用,所以this指向window,打印出来的当然是window下的out
2这个显然是以对象的方法调用,那么this指向该对象
3实际上(inner.func)和inner.func是完全相等的,还是作为对象的方法调用
4赋值表达式和逗号表达式相似,都是返回的值本身,所以也相对于在全局环境下调用函数
*/
this指向
function fn (){
console.log(this)
}
var arr = [fn]
arr[0]()
// 打印arr本身
/*
数组调用了函数,所以this指向数组。类似于obj['fn']()
*/
this指向
var obj = {
name: 'abc',
fn: () => {
console.log(this.name)
}
};
obj.name = 'bcd'
obj.fn() // ''
/*
箭头函数执行的时候上下问时不会绑定this的,它的this取决于外层的this
函数执行的时候外层时全局作用域,this指向window。window对象有name属性为空
*/
解构赋值顺序
let {a, b, c} = {c: 3, b: 2, a: 1}
console.info(a, b, c) // 1, 2, 3
/*
数组解构赋值是按位置对应的,而对象只要变量与属性同名,顺序随意
*/
Object.assgin()合并对象
console.info(Object.assgin([1, 2, 3], [4, 5])) // 4, 5, 3
/*
Object.assgin()合并对象会按对应key合并,合并数组会按下角标合并,将数组的0,1替换
*/
typeof 类型判断js关键字区分大小写运算符优先级
console.info(typeof undefined == typeof NULL) // true
console.info(typeof function(){} == typeof class {}) // true
/*
js关键字区分大小写,第一行是大写NULL,不是小写null。所以就是未声明的普通变量
class是ES6新增语法糖,本质还是函数
*/
var count = 0
console.info(typeof count === 'number') // true
console.info(!!typeof count == 'number') // false
/*
考查运算符优先级,逻辑 !! 的优先级比全等 === 高,先执行!!typeof count,结果为true,然后执行 true === 'number' 结果为false
*/
变量提升构造函数原型连
// 构造函数
function Foo() {
getName = function() {console.info(1)}
return this
}
// 函数方法
Foo.getName = function() {console.info(2)}
// 原型对象方法
Foo.prototype.getName = function() {console.info(3)}
// 函数表达式
var getName = function() {console.info(4)}
// 函数声明
function getName() {console.info(5)}
Foo.getName() // 2
getName() // 4
Foo().getName() // 1
getName() // 1
new Foo.getName() // 2
new Foo().getName() // 3
new new Foo().getName() // 3
// 2 4 1 1 2 3 3
/*
1 执行Foo函数的静态方法,打印2
2 执行getName,因为变量提升以及重新赋值,打印4
3 执行Foo(), 重新赋值全局getName函数,并返回this值,this指向window。打印1
4 因为getName被重新赋值,打印1
5 new操作符是用来调用函数,new Foo.getName() === new (Foo.getName)(), 打印2
6 因为运算符 (.) 的优先级和 new 一样高,所以从左向右执行。new Foo.getName() = (new Foo()).getName()
先创建实例,再通过实例调用原型对象的方法, 打印3
7 new new Foo().getName() = new((new Foo()).getName)()
因为 (.) 与 new级别一样高,所以先 new Foo(), 再执行 (new Foo()).getName ,最后 new ((new Foo()).getName)()
*/