常见的前端常见问题01
1.this指向什么......及call/apply/bind改变this指向8
this是运行期间绑定,和它声明的环境没有关系,只与调用它的对象有关。
1.默认绑定:
var name = 'lufei'
function show() {
var name = 'namei'
console.log(this.name)
}
show()
// lufei
可以看出最后 this 绑定在全局对象上,所以结果是 lufei。
2.隐式绑定:
function show() {
var member = 'namei'
console.log(this.member)
}
var member = 'zoro'
var caomao = {
member: 'lufei',
showMember: show
}
caomao.showMember()
// lufei
这里最后通过 caomao
来调用这个函数,函数中的 this
则被绑定到 caomao
这个对象上。
3.显示绑定:
var caomao = {
member: 'lufei'
}
var member = 'zoro'
function show() {
var member = 'namei'
console.log(this.member)
}
show.call(caomao)
// lufei
通过 call
,apply
,bind
我们可以显示的改变 this
的绑定
4.new
绑定,使用new调用函数,或者说是调用函数时:
function SeaPoacherBoat(member) {
this.member = member
}
var caomao = new SeaPoacherBoat('lufei')
console.log(caomao.member) // lufei
这段代码会执行以下操作:
- 创建一个全新的对象
- 进行原型(
prototype
)链接 - 将 新对象 绑定到函数调用的
this
- 如果没有返回其他对象,则自动返回一个新对象
它们绑定的优先级是
new
> 显示绑定 > 隐式绑定 > 默认,这也是很容易理解的,new
是生成了一个全新的对象优先级是最高的,显示绑定函数要起作用优先级一定要高于隐式绑定,默认绑定是最低的这个也无可厚非。
5.箭头函数中this
普通函数来说,this是运行期间绑定,而ES6新规则里箭头函数没有this的绑定,他是不存在this的绑定的。
// 情景一,全局范围内调用箭头函数
var foo = () => { console.log(this) }
foo() // window
// 情景二,对象中调用
function monkey() {
var bar = () => console.log(this)
bar()
}
var obj = {
monkey: monkey
}
var other = {
obj: obj
}
other.obj.monkey() // { monkey }
之前很多人对箭头函数中的 this
都有一些误解,认为箭头函数中的 this
自身绑定或者是任何绑定在最终调用方式上。其实不然,从上面代码中我们可以看出箭头中的 this
绑定在离最近外层的对象 obj 上, 而不是最终调用对象 other 上。
定时器setTimeout和setIntervar中的this指向window
知道了this的指向,如何修改this的指向呢?
改变this的执行最直接的方法是通过,call
/apply(传一个数组)
/bind(默认不执行)
var name = '草帽海贼团'
var caomao = {
name: '路飞'
}
function printMember(arg1, arg2) {
var name = '娜美'
console.log(this.name)
console.log(arg1, arg2)
}
printMember('山治', '索隆') // 草帽海贼团 山治 索隆
printMember.call(caimao, '山治', '索隆') // 路飞 山治 索隆
printMember.apply(caimao, ['山治', '索隆']) // 路飞 山治 索隆
printMember.bind(caimao, '山治', '索隆')() // 路飞 山治 索隆
根据上面代码,this
现在指向的 window
对象,所以打印的是草帽海贼团而不是娜美 下面我们通过三种方式将 this
指针绑定到 caomao
这个对象,所以最后打印的都是路飞
很明显它们的区别无非就在于形式的不同,以call
为基础来说,apply
是将后面的参数合成一个数组一起传人函数,bind
最后返回的是一个函数,只有调用这个函数后才算执行。
有一种特殊情况就是把 null
和 undefined
作为this
的绑定对象传人进去,这样的实际情况是采用的默认绑定原则 那么这有什么用途呢?常见用于展开数组来,看一段代码
function print(a, b) {
console.log(a, b)
}
print.apply(null, [1, 2])
还有呢, 使用bind
用于柯里化
var foo = print.bind(null, 1)
foo(2)
也就是延迟执行最终的结果
函数的柯里化:
柯里化就是把接受多个参数的函数,变成接受一个参数的函数。
我们可以通过bind方法,第一个参数是形参,后面的参数传入实参。
- 柯理化函数思想:一个js预先处理的思想;利用函数执行可以形成一个不销毁的作用域的原理,把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个小函数,以后我们执行的都是小函数,在小函数中把之前预先存储的值进行相关的操作处理即可;
- 柯里化函数主要起到预处理的作用;
//=============案例一===========
function original(x){
this.a=1;
this.b =function(){return this.a + x}
}
var obj={
a:10
}
var newObj = new (original.bind(obj,2)) //传入了一个实参2//obj是形参
console.log(newObj.a) //输出 1, 说明返回的函数用作构造函数时obj(this的值)被忽略了//new绑定的优先级大于显示绑定的优先级
console.log(newObj.b()) //输出3 ,说明传入的实参2传入了原函数original
//=============案例二===========
var sum = function(x,y) { return x + y };
var succ = sum.bind(null, 1); //让this指向null,其后的实参也会作为实参传入被绑定的函数sum
//null相当于形参没有传值,可以在下面执行是传值。
succ(2) // => 3: 可以看到1绑定到了sum函数中的x
函数的柯里化参考:https://blog.csdn.net/gongzhuxiaoxin/article/details/52628844
2.作用域和作用域链
1.什么是作用域
作用域决定了我们的变量和函数的可访问性。只有2种情况(可访问和不可访问)。我们也可以理解它就是一种规则。
作用域可以分为全局作用域和函数作用域,ES6之后又增加了一个块级作用域。
2.什么是作用域链
如果说作用域是一种规则的话,那么作用域链就是规则的一种体现
函数在调用激活时,会开始创建对应的执行上下文,在执行上下文生存过程中,变量、对象、作用域链以及this的绑定都会分别确定。
作用域链:其实就是当前环境和上一个执行环境的一层层链式关系,通过这种关系我们可以在我们有权访问的情况下,保证对变量的有序访问,避免出现混乱。
作用域是分层的,内层作用域可以访问外层作用域,而外层作用域则不可以访问内层。
参考:https://juejin.im/post/5d155d70f265da1b7638b486#heading-11
3.执行上下文
1.什么是执行上下文
执行上下文就是我们代码当前所处于的环境的一个抽象概念,我们的代码是在一个个的执行上下文中执行的。
(执行环境定义了变量或者函数有权访问的其他的数据,并且也决定了他们各自的行为。)
2.执行上下文的类型:
-
全局执行上下文:
1.创建一个全局对象,在浏览器中全局对象就是window对象;
2.将this的指向指到全局对象window
3.一个程序中执行有一个全局对象
-
函数执行上下文:
每次调用函数时都会创建一个新的执行上下文,每个函数都有自己的执行上下文,但是函数的执行上下文在被调用时才会被创建。一个程序可以有多个函数的执行上下文。
-
eval执行上下文:运行在 eval 函数中的代码也获得了自己的执行上下文,但由于 Javascript 开发人员不常用 eval 函数,所以在这里不谈。
3.执行上下文的声明周期
执行上下文生命周期的三个阶段:1.创建阶段---->2.执行阶段------->3.回收阶段。其中最重要的就是创建阶段。
创建阶段就是:创建变量、对象、作用域链和this的绑定。
4.执行上下文栈
JavaScript引擎创建执行上下文栈来管理执行上下文,可以将执行上下文栈看做是一个存储函数调用的栈的结构,遵循先进后出的原则。
参考:https://juejin.im/post/5d155d70f265da1b7638b486#heading-11
4.类
类存在的目的就是为了生成对象,生存对象的方法有多种,例如:
//工厂函数模式
function createObject(name){
return {
"name": name,
"sayName": function(){
alert(this.name);
}
}
}
但是这样方式有一个显著的问题,我们通过工厂模式生成的各个对象之间并没有联系,没法识别对象的类型,这时候就出现了构造函数。在JavaScript中构造函数和普通的函数没有任何的区别,仅仅是构造函数是通过new操作符调用的。
function Person(name, age, job){
this.name = name;
this.sayName = function(){
alert(this.name);
};
}
var obj = new Person();
obj.sayName();
我们知道new
操作符会做以下四个步骤的操作:
- 创建一个全新的对象
- 新对象内部属性
[[Prototype]]
(非正式属性__proto__
)连接到构造函数的原型 - 构造函数的
this
会绑定新的对象 - 如果函数没有返回其他对象,那么
new
表达式中的函数调用会自动返回这个新对象
这样我们通过构造函数的方式生成的对象就可以进行类型判断。但是单纯的构造函数模式会存在一个问题,就是每个对象的方法都是相互独立的,而函数本质上就是一种对象,因此就会造成大量的内存浪费。回顾new
操作符的第三个步骤,我们新生成对象的内部属性[[Prototype]]
会连接到构造函数的原型上,因此利用这个特性,我们可以混合构造函数模式和原型模式,解决上面的问题。
在ES6中,类中的constructor
函数负担起了之前的构造函数的功能(就是构造函数),类中的实例属性都可以在这里初始化。类的方法sayName
相当于之前我们定义在构造函数的原型上。其实在ES6中类仅仅只是函数的语法糖
5.继承
1.原型继承
我们通过构造函数实例化生成对象,会默认的创建一个—proto—属性连接到构造函数的原型,原型相当于这个对象的父级,对象可以继承父级中的属性方法,同时这个构造函数也可以生成其他的实例化对象,它也会生存一个-proto-属性,连接到构造函数的原型,它也可以继承这个父级的属性和方法。
2.改变this指向继承
我们通过改变this的指向实现继承
3.混合继承
通过构造函数实现继承后,在其他对象中改变this的指向,实现继承,即原型链继承+this指向继承
4.ES6中的extends继承
extends
实现的继承方式可以继承父类的静态成员函数
5.寄生继承
6.XXX
6.原型、原型链
什么是原型
原型就是构造函数的原型对象prototype属性,也可以说是对象自带的隐式的——proto——属性
什么是原型链
一个构造函数通过new方法生成实例对象后,它要调用一个A方法,它会先在他的构造函数中去找,如果没找到这个方法会去它的原型对象protype中去找,还没找到会向protype的原型中去找,直至object的原型对象,这样的一条链式结构我们称为原型链,顶层是null
原型链的继承
比如说我有一个空的函数test,需要继承person构造函数里面的方法,其实就是test的原型对象protype等于了person这个构造函数。
protype是构造函数的原型,--proto--是实例对象的原型
7. 执行多个并发请求
需求:项目必须等待所有的请求结束之后再去执行某一个业务逻辑
以前完全保证二者执行完毕,在一个执行完毕的回调里 执行另一个
思路就是promise回调
我们可以利用axios中的属性,axios.all,它可以执行多个请求,只有请求全部成功的状态下才会加载状态
8.diff算法和虚拟DOM
1.Vue中的diff算法和虚拟DOM(轻量级的javascript对象)
vue中的虚拟DOM:<template>中的内容
diff算法:
1..比较只会在同层级进行, 不会跨层级比较。它会从外层对象开始比较,逐层比较,不夸层级比较。
2.当数据发生变化时,会生成一个新的节点,它会和当前节点进行比较,发现不一样直接渲染到真实的dom节点。也就是数据驱动视图更新
3.有列表的话要写key值,在对比的时候可以一一对应。
4.有则复用,没则创建
2.React中的diff算法和虚拟DOM
React中的虚拟DOM:render函数内的代码
diff算法:
1.有则复用,没则创建
2.比较只会在同层级进行, 不会跨层级比较。
3.有列表的话要写key值,在对比的时候可以一一对应。
4.调用setState改变数据后,react会产生标记dirty ,被标记的节点包括子节点全部会被重新渲染。
如果想要提升性能,可使用 shouldComponentUpdate 钩子函数优化
返回值为 true 表示要更新,返回值为false 不更新(提升性能),默认为返回true
举个例子,vue中diff算法就相当于一个人犯法了,我们就治这个人的罪,而react中这个人就会被株连九族,它的后代全部治罪。