前端学习Day2
1.变量提升
- var声明的全局变量在编译时会提升到作用域的顶部,包含声明和赋值
-
// 这个console会打印一个undefined而不是报错没有声明 console.log(a); var a = 0; -
// 上面的代码编译后相当于这段代码 var a = undefined; console.log(a) a = 0;
-
- let和const声明的变量声明会提升到作用域的顶部,但是不会赋值
-
// 这段代码会报错a没有初始化 console.log(a) let a = 1 -
// 上面的代码编译后会相当于这段代码 let a console.log(a) a = 1
-
- 相同的名称的变量在提升之后,后面的声明会覆盖前面声明
-
// 这段代码a编译完成后 a是一个数字1 而不能被调用,所以会报错 a() var a = function(){ console.log(1) } var a = 1 -
// 上面的代码编译后相当于这段代码 var a = undefined a() a = function(){ console.log(1) } a = 1
-
2.调用栈
调用栈中存储的是执行上下文
- 执行上下文
- 组成
- 1.变量环境:存储var声明,函数声明
- 2.词法环境:存储let,const声明,箭头函数
- 3.执行代码:变量提升完成后执行的代码
- 分类
- 1.全局上下文
- 2.函数上下文
- 每当一个函数被调用时,都会为该函数创建一个新的执行上下文。
- 函数调用完成后,这个函数上下文会出栈
- 3.eval上下文(不用学,避免使用)
3.作用域和作用域链
- 全局作用域
- 全局变量挂载在Window对象上
-
// 这个console会打印出1 var a = 1 console.log(window.a)
- 函数作用域
- 函数内部定义的变量在函数内部生效,函数执行之后会被销毁,不论是var 还是const或者let
- 块级作用域(ES6新增)
- 块级作用域指的是{}的内部
- 在块级作用域中var变量是全局变量,块结束之后仍然存在,let和const变量,块级作用域结束之后会被销毁
- let 和 const 在创建但是未初始化的期间称为
暂时性死区 - 作用域的关系是在编译阶段就决定好的,和函数怎么调用的没有关系
- 作用域链就是用来描述作用域关系的
- 这张图中bar函数的作用域链是 bar->全局 而不是 bar->foo->全局
- 变量的寻找会根据作用域链从内而外寻找
- 面试题目:每隔一秒打印出一个递增的自然数
-
// 这段代码执行后会打印10个10,原因是setTimeout是异步操作,会在同步代码执行完毕之后再执行,i是全局变量,同步代码全部执行后,所有的i都变成了10, for (var i = 0 ; i < 10 ; i ++){ setTimeout(function(){ console.log(i) },1000*i) } -
// 上面的代码编译后相当于这段代码 // 当开始执行定时任务的时候,i已经变成了10 var i = 0; setTimeout(function () { console.log(i) }, 1000 * 0) setTimeout(function () { console.log(i) }, 1000 * 1) setTimeout(function () { console.log(i) }, 1000 * 2) // .... setTimeout(function () { console.log(i) }, 1000 * 9) - 解决办法: 将i声明为let
-
// 改变i为let,得到的代码类似下面的样子,i每次都会重新声明 { let i = 0; setTimeout(function () { console.log(i) }, 1000 * 0) } { let i = 1; setTimeout(function () { console.log(i) }, 1000 * 1) } { let i = 2; setTimeout(function () { console.log(i) }, 1000 * 2) } { let i = 3; setTimeout(function () { console.log(i) }, 1000 * 3) } // ... { let i = 9; setTimeout(function () { console.log(i) }, 1000 * 9) }
-
-
4.闭包
- 闭包的来源:为了在函数执行完成之后仍然能够获取函数中创建的变量.但是又不希望这个变量可以轻易被污染(全局变量容易被污染)
- 闭包就使用函数嵌套函数,内部函数可以访问到外部函数的变量.使用变量使用这个函数,这个函数中的变量就因为被引用而不会被销毁,这也造成了可能的内存泄漏问题
- 闭包实现埋点:
- 埋点:计算某个页面或者按钮的点击次数
-
// 这个闭包实现了点击次数的累加,可以被多次调用,而且各个调用之间的num数字独立 function count() { var num = 0; return function () { return ++num } }
- 闭包实现柯里化
- 柯里化:数学家curry发明了这个理论,减少函数传值个数的方法
-
// 根据传递正则表达值来给出一个特殊的函数 function curryingCheck(reg) { return function (txt) { return reg.test(txt) } } // 先自定义函数,再调用函数实现业务 var isPhone = curryingCheck(/^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/) console.log(isPhone('15810606459')) // true var isEmail = curryingCheck(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/) console.log(isEmail('wyn@nowcoder.com')) // false
5.this
- this解决的问题:可以让对象中的属性方法访问到该对象的其他属性.
- this不同情况的指向
- 严格模式('use strict')情况下,this指向undefined,非严格模式下指向Window对象
- 对象中: this指向该对象
- ES6中箭头函数没有自己的执行上下文,会从自己的父元素继承
- new 关键字构建的新对象中this都是指向这个对象
- 嵌套函数中this不继承,指向外层元素
- 继承this的方法:
- 使用箭头函数
- 使用变量存储this
- 改变this指向的方法
- call方法:
call(this,参数1,参数2,参数3)会直接调用这个方法- call的使用场景
- 对象继承,把自己传给父类,父类进行属性绑定
-
function superClass () { this.a = 1; this.print = function () { console.log(this.a); } } function subClass () { superClass.call(this); this.print(); }
-
- 借用方法
- 对象继承,把自己传给父类,父类进行属性绑定
- call的使用场景
- apply方法:
apply(this,[参数1,参数2,参数3])会直接调用这个方法- apply
- 获取数组元素中的max min
- 合并数组
- apply
- bind方法:
bind(this,参数1,参数2,参数3)不会直接调用,需要手动调用
- call方法: