常见的前端常见问题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

通过 callapplybind 我们可以显示的改变 this 的绑定

4.new绑定,使用new调用函数,或者说是调用函数时:

function SeaPoacherBoat(member) {
    this.member = member
}
var caomao = new SeaPoacherBoat('lufei')
console.log(caomao.member) // lufei

这段代码会执行以下操作:

  1. 创建一个全新的对象
  2. 进行原型(prototype)链接
  3. 将 新对象 绑定到函数调用的 this
  4. 如果没有返回其他对象,则自动返回一个新对象

它们绑定的优先级是 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最后返回的是一个函数,只有调用这个函数后才算执行。

有一种特殊情况就是把 nullundefined 作为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

参考:https://juejin.im/post/5d14bb9a5188255d3f6ca8f6

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操作符会做以下四个步骤的操作:

  1. 创建一个全新的对象
  2. 新对象内部属性[[Prototype]](非正式属性__proto__)连接到构造函数的原型
  3. 构造函数的this会绑定新的对象
  4. 如果函数没有返回其他对象,那么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中这个人就会被株连九族,它的后代全部治罪。

posted @ 2019-08-15 09:05  机智的小恐龙  阅读(146)  评论(0编辑  收藏  举报