zoukankan      html  css  js  c++  java
  • 常见的前端常见问题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中这个人就会被株连九族,它的后代全部治罪。

  • 相关阅读:
    Ubuntu中VisualBox无法识别USB设备
    SpaceVim的基本安装和常见问题
    Linux下配置ssh免密远程登录
    PhantomJS在Selenium中被标记为过时的应对措施
    Linux下Vim编辑器访问系统剪切板
    Linux下将使用rm删除的文件显示在回收站中
    Java基础之创建窗口——使用卡片布局管理器(TryCardLayout)
    Java基础之创建窗口——使用边界布局管理器(TryBorderLayout)
    Java基础之创建窗口——使用流布局管理器(TryFlowLayout)
    Java基础之创建窗口——颜色和光标(TryWindow4)
  • 原文地址:https://www.cnblogs.com/jtjianfeng/p/11355890.html
Copyright © 2011-2022 走看看