一、this指针详解
1、概念
- this是当前函数/当前模块的运行环境上下文,是一个指针型变量,普通函数中的this是在调用时才被绑定确认指向的。
- 通过不同的this调用同一个函数,可以产生不同的结果;
2、this的绑定规则
2.1 默认绑定
function a (){}
a()
函数独立调用的时候,不带任何函数的引用:
- 非严格模式,this指向全局对象(浏览器window,node->global)
- 严格模式,this指向undefined,严格模式下不允许this指向全局对象
var a = 'hi'
var obj = {
a:'miya',
foo:function(){}
}
var bar = obj.foo
bar() //hi
// 严格模式下会报错
tips:普通函数作为参数传递的情况,setTimeout\setInterval,非严格模式下this指向全局对象
var name = 'miya';
var person = {
name:'',
sayHi:sayhi
}
function sayhi(){
console.log(this);// person
setTimeout(function(){
console.log(this.name) //miya
})
}
person.sayHi();
2.2 隐式绑定
与默认绑定相反,函数调用的时候有显式的修饰,比如某个对象调用函数, 链式调用的情况下,this是就近指向的。
2.3 显示绑定
call apply bind 可以修改函数的this指向
call 和apply的异同
- 相同点: 1、都是改变this指向,然后执行原有函数;
2、第一个参数都是作为this的,绑定到函数体的this上,如果不传参数fun.call(),非严格模式下,this会绑定到全局对象;
3、除了this之外,其他的参数的区别
func.call(this,arg1,arg2,...)
func.apply(this,[arg1,arg2,...])
tips:如果給call第一个参数传数字或者字符串会发生什么?
function getThisType(){
console.log(this,typeof this)
}
getThisType.call(1) //[Number:1] object
getThisType.call('lubai') //[String:'lubai']object
判断是否是数组?
Array.isArray()
function isArray(obj){
return Object.prototype.toString.call(obj) === '[object Array]'
}
bind
1、bind方法会创建一个新的函数 当这个新函数被调用时候,bind的第一个参数会作为函数运行时的this,之后的一系列参数都会在传递的实参前传入作为它的参数;
var publicAccount = {
name:'didi',
author;'miya',
subscribe:function(subscriber){
}
}
2.4 new绑定改变this指向
1、创建一个空对象 2、将空对象的__proto__指向原型的prototype 3、以新对象为this执行原有的构造函数 4、return
3、this绑定的优先级(重点)
new绑定> 显示(bind,call,apply)>隐式绑定()>默认绑定()
二、箭头函数
1、特点:
- 箭头函数没有arguments
- 箭头函数没有构造函数 本身没有constructor,所以不能用new来构造实例
- 箭头函数没有原型对象
- 没有自己的this 箭头函数的this是由定义箭头函数的位置决定的,而普通函数是调用的时候才确定的。
2、相关面试题
1、以下函数的输出值
var name = '123';
var obj = {
name: '456',
print: function() {
function a() {
console.log(this.name);
}
a();
}
}
obj.print(); // 123
最终执行的还是a(),默认执行
2、看代码输出
var length = 10;
function fn(){
console.log(this.length)
}
var obj = {
length:5,
method:function(fn){
fn(); // 10
arguments[0](); // 2
}
}
obj.method(fn, 1);
arguments[0]()可以分解成
arguments:{0:fn, 1:1, length:2},相当于调用类数组对象
三、闭包的概念及应用场景
1、什么是闭包?
- 闭包就是指那些能够访问自由变量的函数;
- 自由变量是指函数中使用的,既不是函数参数也不是函数局部变量的变量。
1、从理论⻆度:
所有的函数都是闭包。因为它们都在创建的时候就将上层上下⽂的数据保存起来 了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问⾃由变量, 这个时候使⽤最外层的作⽤域。
2. 从实践⻆度:
以下函数才算是闭包: 即使创建它的上下⽂已经销毁,它仍然存在(⽐如,内部函数从⽗函数中返回) 在代码中引⽤了⾃由变量
2、闭包的作用
- 创建私有变量;
- 延长生命周期;
- ⼀般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引⽤, 即便创建时所在的执⾏上下⽂被销毁,但创建时所在词法环境依然存在,以达到延⻓变量的⽣ 命周期的⽬的
3、应⽤场景
1. 柯⾥化函数
柯⾥化的⽬的在于:避免频繁调⽤具有相同参数函数,同时⼜能够轻松的复⽤。 其实就是封装⼀个⾼阶函数。
// 假设我们有⼀个求⻓⽅形⾯积的函数
function getArea(width, height) {
return width * height
}
// 如果我们碰到的⻓⽅形的宽⽼是10
const area1 = getArea(10, 20)
const area2 = getArea(10, 30)
const area3 = getArea(10, 40)
// 我们可以使⽤闭包柯⾥化这个计算⾯积的函数
function getArea(width) {
return height => { return width * height }
}
const getTenWidthArea = getArea(10)
// 之后碰到宽度为10的⻓⽅形就可以这样计算⾯积
const area1 = getTenWidthArea(20)
// ⽽且如果遇到宽度偶尔变化也可以轻松复⽤
const getTwentyWidthArea = getArea(20)
2、使⽤闭包实现私有⽅法/变量
其实就是模块的⽅式, 现代化的打包最终其实就是每个模块的代码都是相互独⽴的。
function funOne(i){
function funTwo(){
console.log('数字:' + i);
}
return funTwo;
};
var fa = funOne(110);
var fb = funOne(111);
var fc = funOne(112);
fa(); // 输出:数字:110
fb(); // 输出:数字:111
fc(); // 输出:数字:112
3、匿名⾃执⾏函数
var funOne = (function(){
var num = 0;
return function(){
num++; return num;
}
})();
console.log(funOne()); // 输出:1
console.log(funOne()); // 输出:2
console.log(funOne()); // 输出:3
4、缓存⼀些结果
⽐如在外部函数创建⼀个数组, 闭包函数内可以更改/获取这个数组的值,其实还是延⻓变量的 ⽣命周期,但是不通过全局变量来实现。
function funParent(){
let memo = [];
function funTwo(i){
memo.push(i);
console.log(memo.join(','))
}
return funTwo;
};
const fn = funParent();
fn(1);
fn(2);
四、作用域
1、什么是作用域?
- 作⽤域是在运⾏时代码中的某些特定部分中变量,函数和对象的可访问性;
- 换句话说,作⽤域决定了代码区块中变量和其他资源的可⻅性;
- 作⽤域就是⼀个独⽴的地盘,让变量不会外泄、暴露出去。也就是说作⽤域最⼤的⽤处就是隔离变量;
- 不同作⽤域下同名变量不会有冲突;
- ES6 之前 JavaScript 没有块级作⽤域,只有全局作⽤域和函数作⽤域;
- ES6的到来,为我们提供 了块级作⽤域,可通过新增命令let和const来体现。
2、全局作用域
1、在代码中任何地⽅都能访问到的对象拥有全局作⽤域
2、最外层函数 和在最外层函数外⾯定义的变量拥有全局作⽤域
3、所有未定义直接赋值的变量⾃动声明为拥有全局作⽤域
4、所有window对象的属性拥有全局作
3、函数作⽤域
1、函数作⽤域,是指声明在函数内部的变量,和全局作⽤域相反,局部作⽤域⼀般只在固定的代 码⽚段内可访问到,最常⻅的例如函数内部。
2、作⽤域是分层的,内层作⽤域可以访问外层作⽤域的变量,反之则不⾏
4、块级作⽤域
1、块级作⽤域可通过新增命令let和const声明,所声明的变量在指定块的作⽤域外⽆法被访问。
2、块级作⽤域在如下情况被创建:
- 在⼀个函数内部
- 在⼀个代码块(由⼀对花括号包裹)内部 3、let 声明的语法与 var 的语法⼀致。 你基本上可以⽤ let 来代替 var 进⾏变量声明,但会将变量 的作⽤域限制在当前代码块中。
4、块级作⽤域有以下⼏个特点:
- 声明变量不会提升到代码块顶部
- 禁⽌重复声明
- 变量只在当前块内有效
五、作用域链
1、什么是作用域链?
1、有点类似于原型链, 在原型中我们找⼀个属性的时候, 如果当前实例找到,
就会去⽗级原型去找;
2、作⽤域链也是类似的原理, 找⼀个变量的时候, 如果当前作⽤域找不到,
那就会逐级往上去查找, 直到找到全局作⽤域还是没找到,就真找不到了。
2、Tips: 那最先在哪个作⽤域⾥寻找呢?
在执⾏函数的那个作⽤域? 还是在创建函数的作⽤域?
要到创建这个函数的那个域”。 作⽤域中取值,这⾥强调的是“创建”,⽽不是“调⽤”
3、变量声明的规则
变量提升和函数声明提升的规则, 原则上是变量被提升到最顶部, 函数声明被提升到最顶部变量的下⽅。
1、面试题1:看下输出内容
function v() {
var a = 6;
function a() { }
console.log(a);
}
v(); // 6
function v() {
var a;
function a() { }
console.log(a); }
v(); // fn a
解析:
1、js会把所有变量都集中提升到作⽤域顶部事先声明好,
2、但是它赋值的时机是依赖于代码的位 置,那么js解析运⾏到那⼀⾏之后才会进⾏赋值,
还没有运⾏到的就不会事先赋值。
3、也就是会先声明变量,但是变量不会事先赋值
2、面试题2:
function v(){
console.log(a)
var a = 1;
console.log(a)
function a(){}
console.log(a)
console.log(b)
var b = 2
console.log(b)
function b(){}
console.log(b)
}
v()
打印结果依次为:
function a(){}
1
1
function b(){}
2
2
解析: 函数执行的预编译过程分别为:
-
1、变量提升 var a
-
2、函数提升 f a
-
3、打印第一个a,结果是f a(因为函数声明会替换变量声明)
-
4、給变量a赋值为1
-
5、打印第二个a,结果是1
-
6、打印第三个a,结果是1(因为a已赋值)
-
7、后续b是同样的步骤执行。