前端基础知识和一些面试题

CSS基础

  • 三栏布局的问题(左右固定宽度 中间自适应)
  1. float + margin (浮动布局)
<div class="container">
    <div class="left">Left</div>
     <!-- 右栏部分要写在中间内容之前 -->
    <div class="right">Right</div>
    <div class="main">Main</div>
</div>


body,html,.containerl{
    height: 100%;
    padding:0;
    margin: 0;
}
/*左边栏左浮动*/
.left{
    float:left;
    height:100%;
    width:200px;
    background:#333;
}
/*中间栏自适应*/
.main{
    height:100%;
    margin:0 200px;
    background: red;
}
/*右边栏右浮动*/
.right{
    float:right;
    height:100%;
    width:200px;
    background:#333;
}
优点:快捷 简单 兼容性较好
缺点: 有局限性 脱离文档流 需要清除浮动等
  1. position (绝对布局)
<div class="container">
    <div class="left">Left</div>
    <div class="main">Main</div>
    <div class="right">Right</div>
</div>


body,html,.container{
    height: 100%;
    padding: 0;
    margin: 0;
    overflow: hidden;
}
/*左右进行绝对定位*/
.left,.right{
    position: absolute;
    height:100%;
    top: 0;
    background: #333;
}
.left{
    left: 0;
    width: 200px;
}
.right{
    right: 0;
    width: 200px;
}
/*中间用margin空出左右元素所占的空间*/
.main{
    height:100%;
    margin: 0 200px;
    background: red;
}
/*或者中间也进行绝对定位*/
.main{
    position: absolute;
    height:100%;
    left: 200px;
    right:200px;
    background: red;
}

优点:简单粗暴
缺点: 脱离文档流 高度未知会出现问题 可用性差
  1. flex (弹性盒子布局)
<div class="container">
    <div class="left">Left</div>
    <div class="main">Main</div>
    <div class="right">Right</div>
</div>


 body,html{
    height: 100%;
    padding: 0;
    margin: 0;
    overflow: hidden;
}
.container{
    display: flex;
}
.left{
    width:200px;
    background: red;
}
.main{
    flex: 1;
    background: blue;
}
.right{
    width:200px;
    background: red;
}
优点:比较完美 移动端首选
缺点: 不兼容 ie9 及以下
  1. table (表格布局)
<div class="container">
    <div class="left">Left</div>
    <div class="main">Main</div>
    <div class="right">Right</div>
</div>


 body,html{
    height: 100%;
    padding: 0;
    margin: 0;
    overflow: hidden;
}
.container{
    display: table;
    width:100%;
}
.container>div{
    display: table-cell;
}
.left{
    width: 200px;
    background: red;
}
.main{
    background: blue;
}
.right{
    width: 200px;
    background: red;
}
优点:兼容性很好(ie8 及以上) 父元素高度会被子元素撑开(不担心高度塌陷)
缺点: seo 不友好 当其中一个单元格高度超出的时候,其他的单元格也是会跟着一起变高的
  1. Grid (网格布局)
<div class="container">
    <div class="left">Left</div>
    <div class="main">Main</div>
    <div class="right">Right</div>
</div>


 body,html{
    height: 100%;
    padding: 0;
    margin: 0;
    overflow: hidden;
}
.container{
    display: grid;
    width: 100%;
    grid-template-rows: 100px;  /*设置行高*/
    grid-template-columns: 200px auto 200px;  /*设置列数属性*/
}
.left{
    background: red;
}
.main{
    background: blue;
}
.right{
    background:red;
}
优点:简单强大 解决二维布局问题
缺点: 不兼容 ie9 及以下
  • CSS盒模型
    在这里插入图片描述
    标准模型和ie模型
    在这里插入图片描述
  • BFC
    BFC(Block Formatting Context)块级格式化上下文,是 Web 页面中盒模型布局的 CSS 渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。
    BFC 形成条件:
    1、浮动元素,float 除 none 以外的值;
    2、定位元素,position(absolute,fixed);
    3、display 为以下其中之一的值 inline-block,table-cell,table-caption;
    4、overflow 除了 visible 以外的值(hidden,auto,scroll);
    BFC 特性:
    1.内部的 Box 会在垂直方向上一个接一个的放置;
    2.垂直方向上的距离由margin 决定;(解决外边距重叠问题)
    3.bfc 的区域不会与 float 的元素区域重叠;(防止浮动文字环绕)
    4.计算 bfc 的高度时,浮动元素也参与计算;(清除浮动)
    5.bfc 就是页面上的一个独立容器,容器里面的子元素不会影响外面元素;

DOM事件

  • 事件级别
    DOM 0 级:写法:el.οnclick=function(){}

当希望为同一个元素/标签绑定多个同类型事件的时候(如给上面的这个btn元素绑定3个点击事件),是不被允许的。DOM0事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的。

由于DOM 1级中没有事件的相关内容,所以没有DOM 1级事件

DOM 2级写法 :el.addEventListener(evenr-name,callback,useCapture)

event-name:事件名称,可以使标准的DOM事件

callback :回调函数,当事件出发时,函数会被注入一个参数为当前的事件对象event

useCapture:默认是false,代表事件句柄在冒泡阶段执行

DOM 3级 写法和DOM 2级一致 只是在 DOM 2级事件的基础上添加了更多的事件类型

UI事件,当用户与页面上的元素交互时触发,如:load、scroll

焦点事件,当元素获得或失去焦点时触发,如:blur、focus

鼠标事件,当用户通过鼠标在页面执行操作时触发,如:dblclick、mouseup

滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel

文本事件,当在文档中输入文本时触发,如:textInput

键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown,keypress

合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart

变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified

同时DOM 3级 事件也允许使用者自定义一些事件

  • DOM事件模型 事件流

事件模型分为:捕获和冒泡

事件流:

(1)捕获阶段:事件从window对象自上而下向目标节点传播的阶段;
(2)目标阶段:真正的目标节点正在处理事件的阶段;
(3)冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。
在这里插入图片描述

  • 事件委托(代理)
    由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)

优点:

1.减少内存消耗,提高性能(不需要为每一个子元素绑定事件)
2.动态绑定事件
  • Event对象使用
  1. 阻止默认行为:event.preventDefault()

什么是默认事件呢?例如表单一点击提交按钮(submit)跳转页面、a标签默认页面跳转或是锚点定位等

  1. 阻止冒泡:

event.stopPropagation()方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行

stopImmediatePropagation既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其他监听器被触发

  1. event.target & event.currentTarget
<div id="a">
    aaaa
  <div id="b">
      bbbb
    <div id="c">
        cccc
      <div id="d">
          dddd
      </div>
    </div>
  </div>
</div>

<script>
    document.getElementById("a").addEventListener("click", function (e) {
        console.log(
            "target:" + e.target.id + "&currentTarget:" + e.currentTarget.id
        );
    });
    document.getElementById("b").addEventListener("click", function (e) {
        console.log(
            "target:" + e.target.id + "&currentTarget:" + e.currentTarget.id
        );
    });
    document.getElementById("c").addEventListener("click", function (e) {
        console.log(
            "target:" + e.target.id + "&currentTarget:" + e.currentTarget.id
        );
    });
    document.getElementById("d").addEventListener("click", function (e) {
        console.log(
            "target:" + e.target.id + "&currentTarget:" + e.currentTarget.id
        );
    });
</script>

当我们点击最里层的元素d的时候,会依次输出:
target:d&currentTarget:d
target:d&currentTarget:c
target:d&currentTarget:b
target:d&currentTarget:a

由上诉例子可知:event.currentTarget始终是监听事件者,而event.target是事件的真正发出者

  • 自定义事件
创建事件, Event是无法传递参数的
var event = new Event('build');
创建事件, CustomEvent是可以传递参数的
var event = new CustomEvent('build', { detail: elem.dataset.time })

监听事件Listen for the event.
elem.addEventListener('build', function (e) { //... }, false);

分发/触发事件Dispatch the event.
elem.dispatchEvent(event);
  • 手写EventEmitter(发布订阅模式–简单版)
      // 手写发布订阅模式 EventEmitter
      class EventEmitter {
        constructor() {
          this.events = {};
        }
        // 实现订阅
        on(type, callBack) {
          if (!this.events) this.events = Object.create(null);

          if (!this.events[type]) {
            this.events[type] = [callBack];
          } else {
            this.events[type].push(callBack);
          }
        }
        // 删除订阅
        off(type, callBack) {
          if (!this.events[type]) return;
          this.events[type] = this.events[type].filter(item => {
            return item !== callBack;
          });
        }
        // 只执行一次订阅事件
        once(type, callBack) {
          function fn() {
            callBack();
            this.off(type, fn);
          }
          this.on(type, fn);
        }
        // 触发事件
        emit(type, ...rest) {
          this.events[type] &&
            this.events[type].forEach(fn => fn.apply(this, rest));
        }
      }
// 使用如下
      const event = new EventEmitter();

      const handle = (...rest) => {
        console.log(rest);
      };

      event.on("click", handle);

      event.emit("click", 1, 2, 3, 4);

      event.off("click", handle);

      event.emit("click", 1, 2);

      event.once("dbClick", () => {
        console.log(123456);
      });
      event.emit("dbClick");
      event.emit("dbClick");

JS相关

  • 创建对象方式:
// 1.对象字面量
let a={name:'xxx'}

// 2.构造函数
function Person(name){
    this.name=name
}
let b=new Person('xxx')

// 3.Object.create(proto, [propertiesObject])
// Object.create()方法创建的对象时,属性是在原型下面的
let c=Object.create({name:'xxx'})
  • 原型链示意图:
    在这里插入图片描述
  • instanceof原理:

用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性

<script>
function Person(){
}
function Foo(){    
}

//显示改变Foo.prototype指向Person的实例对象(原型继承)

Foo.prototype=new Person()

let a=new Foo()

console.log(a.__proto__===Foo.prototype); //true

console.log(a instanceof Foo);//true

console.log(Foo.prototype.__proto__===Person.prototype);//true

console.log(a instanceof Person);//true

console.log(a instanceof Object);//true

// 这个时候改变Foo.prototype的指向

Foo.prototype={}

// Foo.prototype已经不在a的原型链上面了

console.log(a.__proto__===Foo.prototype);//false

console.log(a instanceof Foo);//false

//Person.prototype依然在a的原型链上面

console.log(a instanceof Person);//true
</script>
  • new运算符原理

1、创建一个空对象

2、让空对象的__proto__(IE没有该属性)成员指向了构造函数的prototype成员对象

3、使用apply调用构造器函数,属性和方法被添加到this引用的对象中

4、如果构造函数中没有返回其他对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象

function _new(func) {
    // 第一步 创建新对象
    let obj= {}; 
    // 第二步 空对象的_proto_指向了构造函数的prototype成员对象
    obj.__proto__ = func.prototype;//
    // 一二步合并就相当于 let obj=Object.create(func.prototype)

    // 第三步 使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中
    let result = func.apply(obj);
    if (result && (typeof (result) == "object" || typeof (result) == "function")) {
    // 如果构造函数执行的结果返回的是一个对象,那么返回这个对象
        return result;
    }
    // 如果构造函数返回的不是一个对象,返回创建的新对象
    return obj;
}
  • JS实现继承的方式
    // 定义一个父类
    function Father(name) {
      // 属性
      this.name = name || "father";
      // 实例方法
      this.sayName = function() {
        console.log(this.name);
      };
      this.color = ["red", "blue"];
    }
    // 原型方法
    Father.prototype.age = 18;
    Father.prototype.sayAge = function() {
      console.log(this.age);
    };
  1. 原型链继承:将父类的实例作为子类的原型
    function Son(name) {
      this.name = name || "son";
    }

    Son.prototype = new Father();

    let s1 = new Son("s1");
    let s2 = new Son("s2");

    s1.color.push("black");

    console.log(s1.name); //s1
    console.log(s1.color); //['red','blue','black']
    console.log(s1.age); //18
    s1.sayAge(); //18
    console.log(s2.name); //s2
    console.log(s2.color); //['red','blue','black']
优点:
1. 简单,易于实现
2. 父类新增原型方法、原型属性,子类都能访问到
缺点:
3. 无法实现多继承,因为原型一次只能被一个实例更改
4. 来自原型对象的所有属性被所有实例共享(上诉例子中的color属性)
5. 创建子类实例时,无法向父构造函数传参
  1. 构造继承:复制父类的实例属性给子类
    function Son(name) {
      Father.call(this, "我是传给父类的参数");
      this.name = name || "son";
    }
    let s = new Son("son");
    console.log(s.name); // son
    //s.sayAge(); // 抛出错误(无法继承父类原型方法)
    s.sayName(); // son
    console.log(s.age); // undefined (无法继承父类原型属性)
    console.log(s instanceof Father); // false
    console.log(s instanceof Son); // true
优点:
1. 解决了原型链继承中子类实例共享父类引用属性的问题
2. 创建子类实例时,可以向父类传递参数
3. 可以实现多继承(call多个父类对象)
缺点:
4. 实例并不是父类的实例,只是子类的实例
5. 只能继承父类实例的属性和方法,不能继承其原型上的属性和方法
6. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
  1. 组合继承:将原型链和借用构造函数的技术结合到一块。使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承
 function Son(name) {
     // 第一次调用父类构造器 子类实例增加父类实例
      Father.call(this, "我是传给父类的参数");
      this.name = name || "son";
    }
    // 经过new运算符 第二次调用父类构造器 子类原型也增加了父类实例
    Son.prototype = new Father();

    let s = new Son("son");
    console.log(s.name); // son
    s.sayAge(); // 18
    s.sayName(); // son
    console.log(s.age); // 18
    console.log(s instanceof Father); // true
    console.log(s instanceof Son); // true
    console.log(s.constructor === Father); // true
    console.log(s.constructor === Son); // false
优点:
1. 弥补了构造继承的缺点,现在既可以继承实例的属性和方法,也可以继承原型的属性和方法
2. 既是子类的实例,也是父类的实例
3. 可以向父类传递参数
4. 函数可以复用
缺点:
1. 调用了两次父类构造函数,生成了两份实例
2. constructor指向问题
  1. 实例继承:为父类实例添加新特征,作为子类实例返回
     function Son(name) {
      let f=new Father('传给父类的参数')
      f.name=name||'son'
      return f
    }

    let s = new Son("son"); //或者直接调用子类构造函数 let s = Son("son");
    console.log(s.name); // son
    s.sayAge(); // 18
    s.sayName(); // son
    console.log(s.age); // 18
    console.log(s instanceof Father); // true
    console.log(s instanceof Son); // false
    console.log(s.constructor === Father); // true
    console.log(s.constructor === Son); // false
优点:
1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
缺点:
1. 实例是父类的实例,不是子类的实例
2. 不支持多继承
  1. 拷贝继承:对父类实例中的方法与属性拷贝给子类的原型
    function Son(name) {
      let f = new Father("传给父类的参数");
      for (let k in f) {
        Son.prototype[k] = f[k];
      }
      Son.prototype.name = name;
    }

    let s = new Son("son");
    console.log(s.name); // son
    s.sayAge(); // 18
    s.sayName(); // son
    console.log(s.age); // 18
    console.log(s instanceof Father); // false
    console.log(s instanceof Son); // true
    console.log(s.constructor === Father); // false
    console.log(s.constructor === Son); // true
优点:
1. 支持多继承
缺点:
1. 效率低,性能差,占用内存高(因为需要拷贝父类属性)
2. 无法获取父类不可枚举的方法(不可枚举的方法,不能使用for-in访问到)
  1. 寄生组合继承:通过寄生方式,砍掉父类的实例属性,避免了组合继承生成两份实例的缺点
    function Son(name) {
      Father.call(this);
      this.name = name || "son";
    }

    // 方法一  自己动手创建一个中间类
    // (function() {
    //   let NoneFun = function() {};
    //   NoneFun.prototype = Father.prototype;
    //   Son.prototype = new NoneFun();
    //   Son.prototype.constructor = Son;
    // })();

    // 方法二  直接借用Object.create()方法
    Son.prototype = Object.create(Father.prototype);
    // 修复构造函数指向
    Son.prototype.constructor = Son;

    let s = new Son("son");
    console.log(s.name); // son
    s.sayAge(); // 18
    s.sayName(); // son
    console.log(s.age); // 18
    console.log(s instanceof Father); // true
    console.log(s instanceof Son); // true
    console.log(s.constructor === Father); // false
    console.log(s.constructor === Son); // true
优点:
1. 比较完美(js实现继承首选方式)
缺点:
1.实现起来较为复杂(可通过Object.create简化)
  1. es6–Class继承:使用extends表明继承自哪个父类,并且在子类构造函数中必须调用super
    class Son extends Father {
      constructor(name) {
        super(name);
        this.name = name || "son";
      }
    }

    let s = new Son("son");
    console.log(s.name); // son
    s.sayAge(); // 18
    s.sayName(); // son
    console.log(s.age); // 18
    console.log(s instanceof Father); // true
    console.log(s instanceof Son); // true
    console.log(s.constructor === Father); // false
    console.log(s.constructor === Son); // true
  • JS防抖与节流(性能优化)
    防抖:动作停止后的时间超过设定的时间后执行一次函数。注:这里的动作停止表示你停止了触发这个函数,从这个时间点开始计算,当间隔时间等于你设定的时间,才会执行里面的回调函数。如果你一直在触发这个函数并且两次触发间隔小于设定时间,则函数一直不会执行
简单实现:

function debance(fn, delay) {
let timer = null;
return () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn();
}, delay);
};
}

window.addEventListener(
“scroll”,
debance(() => {
console.log(111);
}, 1000)
);

防抖应用场景:
1. search搜索联想,用户在不断输入值时,用防抖来节约请求资源
2. window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

节流:在一定时间内执行的操作只执行一次,也就是预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期

简单实现:
    //方法一:设置一个标志
    function throttle(fn, delay) {
      let flag = true;
      return () => {
        if (!flag) return;
        flag = false;
        timer = setTimeout(() => {
          fn();
          flag = true;
        }, delay);
      };
    }
    //方法二:使用时间戳
  function throttle(fn, delay) {
      let startTime=new Date()
      return () => {
        let endTime=new Date()
        if (endTime-startTime>=delay){
          fn()
          startTime=endTime
        }else{
          return
        }
      };
    }
    window.addEventListener(
      "scroll",
      throttle(() => {
        console.log(111);
      }, 1000)
    );
    
节流应用场景:
1. 鼠标不断点击触发,mousedown(单位时间内只触发一次)
2. 监听滚动事件,比如是否滑到底部自动加载更多(懒加载),用throttle来判断
  • JS运行机制

JS为啥是单线程

js作为浏览器脚本语言,其主要用途是与用户互动,以及操作DOM。这就决定了它只能是单线程,否则会带来很复杂的同步问题。(假设JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这是李璐蓝旗应该以那个线程为准?)

JS同步任务和异步任务
在这里插入图片描述
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript语言的设计者意识到这个问题,将所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

任务队列(消息队列)
在这里插入图片描述
任务队列中存着的是异步任务,这些异步任务一定要等到执行栈清空后才会执行。
异步任务,会先到事件列表中注册函数。如果事件列表中的事件触发了,会将这个函数移入到任务队列中(DOM操作对应DOM事件,资源加载操作对应加载事件,定时器操作可以看做对应一个“时间到了”的事件)

宏任务与微任务
在这里插入图片描述
macro-task(宏任务):包括整体代码script,setTimeout,setInterval, setImmediate, I/O, UI rendering
micro-task(微任务):Promise,process.nextTick,MutationObserver
微任务意义:

减少更新时的渲染次数,因为根据HTML标准,会在宏任务执行结束之后,在下一个宏任务开始执行之前,UI都会重新渲染。如果在microtask中就完成数据更新,当 macro-task结束就可以得到最新的UI了。如果新建一个 macro-task来做数据更新的话,那么渲染会执行两次

Event Loop(事件循环)
在这里插入图片描述

  1. 整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为两部分:“同步任务”、“异步任务”;
  2. 同步任务会直接进入主线程依次执行;
  3. 异步任务会再分为宏任务和微任务;
  4. 宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
  5. 微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
  6. 当主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务;
  7. 上述过程会不断重复,这就是Event Loop事件循环;

一图总结(事件循环、执行栈、任务队列、宏任务、微任务)
在这里插入图片描述
经典面试题

console.log(1);

setTimeout(()=>{
    console.log(2);   
    new Promise((resolve,reject)=>{
    console.log(3);
    resolve()
}).then(res=>{
    console.log(4); 
})
})

new Promise((resolve,reject)=>{
    resolve()
}).then(res=>{
    console.log(5); 
}).then(res=>{
    console.log(6);
    
})

new Promise((resolve,reject)=>{
    console.log(7);
    resolve()
}).then(res=>{
    console.log(8); 
}).then(res=>{
    console.log(9);
    
})

setTimeout(()=>{
    console.log(10);   
    new Promise((resolve,reject)=>{
    console.log(11);
    resolve()
}).then(res=>{
    console.log(12); 
})
})

console.log(13);

依次输出
1
7
13
5
8
6
9
2
3
4
10
11
12

  • 如何取消promise
      // 方法一 取消promise方法   promise.race方法
      function wrap(p) {
        let obj = {};
        let p1 = new Promise((resolve, reject) => {
          obj.resolve = resolve;
          obj.reject = reject;
        });
        obj.promise = Promise.race([p1, p]);
        return obj;
      }

      let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(123);
        }, 1000);
      });
      let obj = wrap(promise);
      obj.promise.then(res => {
        console.log(res);
      });
      obj.resolve("请求被拦截了");

      obj.reject("请求被拒绝了");


      //方法二 取消promise方法   新包装一个可操控的promise

      function wrap(p) {
        let res = null;
        let abort = null;

        let p1 = new Promise((resolve, reject) => {
          res = resolve;
          abort = reject;
        });

        p1.abort = abort;
        p.then(res, abort);

        return p1;
      }

      let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(123);
        }, 1000);
      });
      let obj = wrap(promise);
      obj.then(res => {
        console.log(res);
      });
      obj.abort("请求被拦截");

HTTP协议相关

HTTP(Hyper Text Transfer Protocol)<超文本传输协议>的缩写.是用于从WWW服务器传输超文本到本地浏览器的传输协议.HTTP是一个应用层协议,由请求和响应构成,是一个标准的个客户端和服务器模型

  • 特点
  1. 基于请求 / 相应模型的协议
  2. 简单快速
  3. 灵活
  4. 无连接
  5. 无状态
  • HTTP报文

请求报文:
在这里插入图片描述

相应报文:
在这里插入图片描述

  • HTTP
    在这里插入图片描述
    get和post区别
GET请求会被浏览器主动cache,而POST不会,除非手动设置

get把请求的参数放在url上,即HTTP协议头上 post把参数放在HTTP的包体内

Get 方式传输的数据量非常小,一般限制在 2 KB 左右,但是执行效率却比 >Post 方法好;而 Post
方式传递的数据量相对较大,它是等待服务器来读取数>据,不过也有字节限制(实际上IIS4中最大量为80KB,IIS5中为100KB),这是为了避免对服务器用大量数据进行恶意攻击

GET请求只能进行url编码,而POST支持多种编码方式

GET产生的URL地址可以加入书签,而POST不可以

GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留

GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
  • HTTP状态吗

状态码:由3位数字组成,第一个数字定义了响应的类别

1xx:指示信息,表示请求已接收,继续处理

2xx:成功,表示请求已被成功接受,处理。

200 OK:客户端请求成功
204 No Content:无内容。服务器成功处理,但未返回内容。一般用在只是客户端向服务器发送信息,而服务器不用向客户端返回什么信息的情况。不会刷新页面。
206 Partial Content:服务器已经完成了部分GET请求(客户端进行了范围请求)。响应报文中包含Content-Range指定范围的实体内容

3xx:重定向

301 Moved Permanently:永久重定向,表示请求的资源已经永久的搬到了其他位置。
302 Found:临时重定向,表示请求的资源临时搬到了其他位置
303 See Other:临时重定向,应使用GET定向获取请求资源。303功能与302一样,区别只是303明确客户端应该使用GET访问
307 Temporary Redirect:临时重定向,和302有着相同含义。POST不会变成GET
304 Not Modified:表示客户端发送附带条件的请求(GET方法请求报文中的IF…)时,条件不满足。返回304时,不包含任何响应主体。虽然304被划分在3XX,但和重定向一毛钱关系都没有

4xx:客户端错误

400 Bad Request:客户端请求有语法错误,服务器无法理解。
401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。
403 Forbidden:服务器收到请求,但是拒绝提供服务
404 Not Found:请求资源不存在。比如,输入了错误的url
415 Unsupported media type:不支持的媒体类型

5xx:服务器端错误,服务器未能实现合法的请求。

500 Internal Server Error:服务器发生不可预期的错误。
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常

  • HTTP持久化连接与管道化

在HTTP1.0中,默认的是短连接,没有正式规定 Connection:Keep-alive 操作;

HTTP/1.1所有连接都是Keep-alive的,也就是默认都是持续连接的。在事务处理结束之后仍然保持在打开状态的TCP连接称之为持久连接。

持久连接详解:

持久连接会在不同事务之间保持打开状态,直到客户端或服务器决定其关闭为止。重用已对目标服务器打开的空闲持久连接,就可以避开缓慢的连接建立阶段。而且,已经打开的连接还可以避免慢启动的拥塞适应阶段,以便更快速地进行数据传输。所以,持久连接降低了时延和连接建立的开销,将连接保持在已调谐状态,而且减少了打开连接的潜在数量

HTTP/1.1允许在持久连接上可选的使用请求管道,是相对于keep-alive连接的又一性能优化。在响应到达之前,可以将多条请求放入队列,当第一条请求通过网络流向服务器时,第二条和第三条请求也可以开始发送了。在高时延网络条件下,这样做可以降低网络的环回时间,提高性能

管道连接注意点:

1)如果HTTP客户端无法确认连接是持久的,就不应该使用管道
2)必须按照与请求相同的顺序回送HTTP响应。
3)HTTP客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成管道化的请求。
4)出错的时候,管道连接会阻碍客户端了解服务器执行的是一些列管道化请求中的哪一些。由于无法安全地重试POST这样的非幂请求,所以出错时,就存在某些方法永远不会被执行的风险。

在这里插入图片描述

前端通信

  • 同源策略

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的安全机制。

什么是源:协议、域名与端口。这三者任何一个不一样的话,就算是跨域

什么是限制:不是一个源的文档,没有权限去操作另一个源的文档

Cookie、LocalStorage 和 IndexDB无法读取。
Dom无法获得
Ajax请求不能发送
  • 前后端通信方式
  1. Ajax支持同源通信
  2. WebSocket不受同源策略影响
  3. CORS既支持同源也支持跨域通信
  • 如何创建ajax
util.json = function (options) {
     var opt = {
         url: '',
         type: 'get',
         data: {},
         success: function () {},
         error: function () {},
     };
     util.extend(opt, options);
     if (opt.url) {
         var xhr = XMLHttpRequest
            ? new XMLHttpRequest()
            : new ActiveXObject('Microsoft.XMLHTTP');
         var data = opt.data,
             url = opt.url,
             type = opt.type.toUpperCase(),
             dataArr = [];
         for (var k in data) {
             dataArr.push(k + '=' + data[k]);
         }
         if (type === 'GET') {
             url = url + '?' + dataArr.join('&');
             xhr.open(type, url.replace(/\?$/g, ''), true);
             xhr.send();
         }
         if (type === 'POST') {
             xhr.open(type, url, true);
             xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
             xhr.send(dataArr.join('&'));
         }
         xhr.onload = function () {
             if (xhr.status === 200 || xhr.status === 304) {
                 var res;
                 if (opt.success && opt.success instanceof Function) {
                     res = xhr.responseText;
                     if (typeof res === 'string') {
                         res = JSON.parse(res);
                         opt.success.call(xhr, res);
                     }
                 }
             } else {
                 if (opt.error && opt.error instanceof Function) {
                     opt.error.call(xhr, res);
                 }
             }
         };
     }
 };

  • 跨域通信的几种方式
  1. JSONP(只支持GET请求)

通过script标签的异步加载来实现的。利用script标签不受同源策略的限制,天然可以跨域的特性。

<script>
var script = document.createElement('script');
script.type = 'text/javascript';

script.src = 'https://api.asilu.com/geo/&callback=jsonp';//这个是获取当前经纬度的接口
document.head.appendChild(script);//创建并添加script标签到<head>下

// 回调执行函数
function jsonp(res) {
    console.log(res);//打印jsonp返回的信息
}
</script>
  1. Hash

url的#后面的内容就叫Hash。Hash的改变,页面不会刷新。

// 在A中伪代码如下:
var B = document.getElementsByTagName('iframe');
B.src = B.src + '#' + 'data';

// 在B中的伪代码如下
window.onhashchange = function () {
  var data = window.location.hash;
};
  1. postMessage

H5中新增的postMessage()方法,可以使用来做跨域通信

// 在A窗口中操作如下:向B窗口发送数据
    Bwindow.postMessage('data', 'http://B.com'); //这里强调的是B窗口里的window对象

// 在窗口B中监听 message 事件
    Awindow.addEventListener('message', function (event) {   //这里强调的是A窗口里的window对象
        console.log(event.origin);  //获取 :A窗口url
        console.log(event.source);  //获取:A window对象
        console.log(event.data);    //获取传过来的数据
    }, false);
  1. WebSocket

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现

var ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = function (evt) {
  console.log('Connection open ...');
  ws.send('Hello WebSockets!');
};
ws.onmessage = function (evt) {
  console.log('Received Message: ', evt.data);
  ws.close();
};
ws.onclose = function (evt) {
  console.log('Connection closed.');
};
  1. CORS(现代浏览器普通跨域解决方案)

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但是用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信

前端安全

  • XSS攻击(跨站脚本攻击)

英文全程:Cross Site Script,XSS攻击,通常指黑客通过“HTML注入”篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击

XSS的分类

1.反射型XSS

原理:反射型XSS,也叫做非持久型XSS,实质发送请求时,XSS代码出现在请求URL中,作为参数提交到服务器,服务器解析并响应。响应结果中包含XSS代码,最后浏览器解析并执行

实现:攻击者通过给用户发送带有恶意脚本代码参数的URL,当URL地址被打开时,特有的恶意代码参数被HTML解析、执行。
在这里插入图片描述

  1. 存储型XSS

原理:一般是攻击者输入的恶意代码”存储“在服务器端,主要是将XSS代码发送到服务器(不管是数据库、内存还是文件系统等。),只要受害者浏览器包含此恶意代码的页面就会执行恶意代码。

实现:存储型XSS一般出现在网站留言、评论、博客日志等交互处。例如:黑客提交了一条包含XSS代码的留言到数据库。当目标用户查询留言时,那些留言的内容会从服务器解析之后加载出来。浏览器发现有XSS代码,就当做正常的HTML和JS解析执行。XSS攻击就发生了。

XSS的防御

HttpOnly
浏览器禁止页面的Javascript访问带有HttpOnly属性的cookie。(实质解决的是:XSS后的cookie劫持攻击)如今已成为一种“标准”的做法

输入检查(XSS Filter)
让一些基于特殊字符的攻击失效。(常见的Web漏洞如XSS、SQLInjection等,都要求攻击者构造一些特殊字符)

输出检查
在变量输出到HTML页面时,使用编码或转义的方式来防御XSS攻击
  • CSRF(跨站请求伪造)

CSRF就是利用你所在的网站的登陆的状态,以你的名义向网站发送恶意请求。CSRF能做的事情包括利用你的身份发邮件、发短信、进行交易转账等,盗取你的账号,甚至购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全

原理图:

在这里插入图片描述
例子:

用户登录自己的博客网站(本地cookie已经保存了登录信息)

攻击者构造一个页面:http://www.a.com/csrf.html

其内容为<img src="http://blog.sohu.com/manage/entry.do?m=deleted&id=156714243" />

使用了一个img标签,其地址指向了删除Iid为156714243的博客文章

然后攻击者诱使用户访问这个页面

用户进去看到一张无法显示的图片,这时自己的那篇博客文章已经被删除了

关键点:

  1. 用户登陆受信任网站A,并且在本地生成Cookie
  2. 在不登出网站A的情况下,访问危险网站B

CSRF的防御

  1. 增加token在请求中放入攻击者所不能伪造的信息,并且该信总不存在于cookie之中。鉴于此,系统开发人员可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务端进行token校验,如果请求中没有token或者token内容不正确,则认为是CSRF攻击而拒绝该请求
  2. 通过Referer识别 根据HTTP协议,在HTTP头中有一个字段叫Referer,他记录了该HTTP请求的来源地址。在通常情况下,访问一个安全受限的页面的请求都来自同一个网站
  3. 网站重要操作增加验证码CSRF攻击过程中,用户在不知情的情况下构造了网络请求,添加验证码后,强制用户必须与应用进行交互

渲染机制

  • DOCTYPE作用

DTD(document type define,文档类型定义)是一系列的语法规则,用来定义XML或(X)HTML的文件类型。浏览器会使用它来判断文档类型,决定使用何种协议来解析,以及切换浏览器模式

DOCTYPE是用来声明文档类型和DTD规范的,一个主要的用途是文件的合法性验证。如果文件代码不合法,那么浏览器解析时便会出一些差错

  • 浏览器渲染过程
    在这里插入图片描述
  1. 解析HTML,生成DOM树(DOM)
  2. 解析CSS,生成CSSOM树(CSSOM)
  3. 将DOM和CSSOM合并,生成渲染树(Render-Tree)
  4. 计算渲染树的布局(Layout)
  5. 将布局渲染到屏幕上(Paint)

几个关键概念

CSS阻塞渲染:由于CSSOM负责存储渲染信息,浏览器就必须保证在合成渲染树之前,CSSOM是完备的,这种完备是指所有的CSS(内联、内部和外部)都已经下载完,并解析完,只有CSSOM和DOM的解析完全结束,浏览器才会进入下一步的渲染。CSS阻塞渲染意味着,在CSSOM完备前,页面将一直处理白屏状态,这就是为什么样式放在head中,仅仅是为了更快的解析CSS,保证更快的首次渲染。

JS阻塞页面:JS可以操作DOM来修改DOM结构,可以操作CSSOM来修改节点样式,这就导致了浏览器在解析HTML时,一旦碰到script,就会立即停止HTML的解析,也阻塞了其后的CSS解析,整个解析进程必须等待JS的执行完成才能够继续。从性能角度上讲,将script放在页面底部,也就合情合理了

重排(Reflow):DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow

触发重排(reflow):

  • 1、当增加、删除、修改DOM节点时,会导致reflow或repaint
  • 2、当移动DOM的位置,或是插入动画的时候
  • 3、当修改CSS样式的时候
  • 4、当Resize窗口的时候,或是滚动的时候
  • 5、当修改网页的默认字体时

重绘(Repaint):当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint。

触发重绘(Repaint):

  • 1、DOM改动
  • 2、CSS改动

最小化重绘和重排
1.一次性修改样式:减少内联样式使用 样式合并写法
2.批量修改DOM:使用文档片段创建一个子树,然后再拷贝到文档中(document.fragment)
3.缓存布局信息

//每次需要查询div.offsetLeft 浪费性能
div.style.left = 1 + div.offsetLeft + 'px';
div.style.top = 1 + div.offsetTop + 'px';

//将这个值保存下来,避免重复取值 性能优化
current = div.offsetLeft;
div.style.left = 1 + ++current + 'px';
div.style.top = 1 + ++current + 'px';

性能优化

Yahoo Developer Network–包含 7 个类别共 35 条前端性能优化最佳实践

在这里插入图片描述

前端常用的方法

  1. 资源合并压缩 减少HTTP请求
    1.1尽量合并和压缩html css和js文件 借助前端工具 例如 webpack gulp grunt…
    1.2 开启gzip压缩

  2. 图片优化
    雪碧图 图片压缩 svg base64

  3. 懒加载 / 预加载
    3.1 懒加载:图片进入可视区域之后请求图片资源 对于电商等图片很多,页面很长的业务场景适用 并发加载的资源过多会阻塞 js 的加载,影响网站的正常使用
    3.2 预加载:图片等静态资源在使用之前的提前请求 资源使用到时能从缓存中加载,提升用户体验

  4. 浏览器存储 localStorage:大小为 5M 左右仅在客户端使用,不和服务端进行通信 浏览器本地缓存方案 indexedDB:用于客户端存储大量结构化数据 为应用创建离线版本

  5. 浏览器缓存
    5.1强缓存:
    (1)expires: 缓存过期时间,用来指定资源到期的时间,是服务器端的绝对时间 告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求
    (2)cache-control:max-age = xxx 声明该资源在加载后的xxx秒内都直接使用缓存 使用的是相对时间 即加载文件本机的时间
    在这里插入图片描述
    如果在Cache-Control响应头设置了 “max-age” 或者 “s-max-age” 指令,那么 Expires 头会被忽略。

    5.2协商缓存:

触发条件
Cache-Control 的值为 no-cache (不强缓存)
max-age 过期了 (强缓存,但总有过期的时候)

(1)Last-Modified / If-Modified-Since
Last-Modified ------- response header
If-Modified-Since ------- request header
缺点:某些服务端不能获取精确的修改时间;文件修改时间改了,但文件内容却没有变

(2)Etag / If-None-Match

文件内容的 hash 值

etag ------- response header

if-none-match ------- request header

  1. CDN内容分发网络
  2. 优化js代码结构,减少冗余代码
  3. 减少http请求,合理设置 HTTP缓存
  4. 避免重定向

前端错误监控以及上报

  1. 前端错误分类:
1.即时运行错误:代码错误
2.资源加载错误
3.对于跨域的代码运行错误会显示 Script error. 对于这种情况我们需要给 script 标签添加 crossorigin 属性,并且服务器添加Access-Control-Allow-Origin
  1. 即时运行错误捕获
    (1)try …catch
    (2) window.onerror 或者 window.addEventListener 记住事件捕获阶段获得,不是冒泡阶段

  2. 资源加载错误
    (1)object.onerror,如img.onerror
    (2)performance.getEntries (getEntries api返回一个资源加载完成数组,假设为img,再查询页面中一共有多少个img,二者的差就是没有加载上的资源)
    (3)Error事件捕获

  3. 错误如何上报
    (1)ajax
    (2)image的src上报

(new Image()).src = ‘错误上报的请求地址’

一般来说,大厂都是采用利用image对象的方式上报错误的;使用图片发送get请求,上报信息,由于浏览器对图片有缓存,同样的请求,图片只会发送一次,避免重复上报

前端模块化

模块化就是将一个复杂的系统分解成多个独立的模块的代码组织方式。

在很长的一段时间里,前端只能通过一系列的script标签来维护我们的代码关系,但是一旦我们的项目复杂度提高的时候,这种简陋的代码组织方式便是如噩梦般使得我们的代码变得混乱不堪。所以,在开发大型Javascript应用程序的时候,就必须引入模块化机制。

由于早期官方并没有提供统一的模块化解决方案,所以在群雄争霸的年代,各种前端模块化方案层出不穷。

前端模块化发展之路: IIFE(自执行函数)>>AMD(RequireJS实现)>>CMD(SeaJS实现)>>CommonJS(NodeJs)>>ES6 Modules(模块化直接成为了Javascript语言规范中的一部分)

在这里插入图片描述

一些面试题

1.列举你知道的HTTP请求和响应头的字段和该字段的作用。

http请求头
Accept:告诉服务器,客户机支持的数据格式 (*/都接受,text/html/)
Accept-Charset:用于告诉服务器,客户机采用的编码
Accept-Encoding:用于告诉服务器,客户机支持的数据压缩格式
Accept-language:用于告诉服务器,客户机的语言环境
Host:用于告诉服务器,客户机想访问的主机名
If-Modified-Since:用于告诉服务器,资源缓存的时间
Referer:用于告诉服务器,客户机是从哪个资源链接到本资源的(防盗链)
User-Agent:用于告诉服务器,客户机的软件环境
Cookie:客户机通过这个头可以想服务器带数据

http响应头
Location:这个响应头配合302状态码使用,用于告诉客户机资源的地址
Server:服务器通过这个头,告诉浏览器服务器的类型
Content-Encoding:服务器通过这个头,告诉浏览器数据压缩的格式
Content-Length:服务器通过这个头,告诉浏览器回送数据的长度
Content-Type:服务器通过这个头,告诉浏览器回送数据的类型
Last-Modified:服务器通过这个头,告诉浏览器当前资源缓存时间
Refresh:服务器通过这个头,告诉浏览器隔多长时间刷新一次
Content-Disposition:服务器通过这个头,告诉浏览器以下载方式打开数据
Transfer-Encoding:服务器通过这个头,告诉浏览器数据传送的格式
ETag:缓存相关的头,和Last-Modified功能一样,不过实时性更强(Last-Modified是一秒内即使内容更新也让浏览器找缓存)
Expires:服务器通过这个头,告诉浏览器把回送的资源缓存多长时间,0或-1表示不缓存
Cache-Control:no-cache
Pragma: no-cache

2.HTTP是无状态协议,那么网站应该如何记住用户的登录状态?尽量详细回答整个过程。

可以使用session来记录用户登录状态,session是服务器保存数据,当用户登录时,服务器会生成一个seesionid,这个sessionid下面就存储着用户的所有信息,这个时候也会传递这个sessionid给浏览器,这个sessionid存储于浏览器cookie中,之后用户请求的时候,就会把这个sessionid与服务器匹配,找到相对应的信息。

3.写出几种 IE6 BUG 的解决方法

1.双边距 BUG float 引起的 使用 display
2.像素问题 使用 float 引起的 使用 dislpay:inline -3px
3.超链接 hover 点击后失效 使用正确的书写顺序 link visited hover ac
tive
4.Ie z-index 问题 给父级添加 position:relative
5.Png 透明 使用 js 代码 改
6.Min-height 最小高度 !Important 解决’
7.select 在 ie6 下遮盖 使用 iframe 嵌套
8.为什么没有办法定义 1px 左右的宽度容器(IE6 默认的行高造成的,使用 over:hidden,zoom:0.08 line-height:1px)

4.Doctype 作用? 严格模式与混杂模式如何区分?它们有何意义?

(1)、<!DOCTYPE> 声明位于文档中的最前面,处于 标签之前。告知浏览器的解析器,
用什么文档类型 规范来解析这个文档。
(2)、严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。
(3)、在混杂模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的
行为以防止站点无法工作。
(4)、DOCTYPE 不存在或格式不正确会导致文档以混杂模式呈现。

5.iframe 有那些缺点?

  • iframe 会阻塞主页面的 Onload 事件;
  • iframe 和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。
    使用 iframe 之前需要考虑这两个缺点。如果需要使用 iframe,最好是通过 javascript动态给 iframe 添加 src 属性值,这样可以可以绕开以上两个问题。

6.display 与 visibility 有何异同?

可以使用 display 属性定义建立布局时元素生成的显示框类型。

如果将 display 属性设置为 block,可以让行内元素(比如< a > 素)表现得像块级元素一样;

如果将 display 属性设置为 inline,可以让块级元素(比如< p > 元素)表现得像内联元素一样;

可以通过把 display 属性设置为 none,让生成的元素根本没有框。这样的话,该框及其所有内容就不再显示,不占用文档中的空间。

在 DIV 设计中,使用 display:none 属性后,HTML 元素(对象)的宽度、高度等各种属性值都将“丢失”;而使用 visibility:hidden 属性后,HTML 元素(对象)仅仅是在视觉上看不见(完全透明),而它所占据的空间位置仍然存在,也即是说它仍具有高度、宽度等属性值。

7.清除浮动的几种方式,各自的优缺点 如何清除浮动元素带来的影响?

  1. 使用空标签清除浮动 clear:both(理论上能清楚任何标签,,,增加无意义的标签)
  2. 使用 overflow:auto(空标签元素清除浮动而不得不增加无意代码的弊端,使用 zoom:1 用于兼容 IE)
  3. 是用 afert 伪元素清除浮动(用于非 IE 浏览器)

浮动定位是指将元素排除在普通流之外,并且将它放置在包含框的左边或者右边,但是依旧位于包含框之内。也就是说,浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止。

由于浮动框不在文档的普通流中,所以元素浮动之后,其原有位置不再保留,其他元素的位置会受到影响。

如果需要清除左侧或者右侧浮动元素带来的影响,则可以使用 clear 属性来设置。另外,包含框内的子元素浮动后,如果包含框没有设置具体的高度,则其高度会发生变化,此时,可以使用overflow 属性来清除子元素浮动后带来的影响。

8.简要描述 JavaScript 中的作用域链

任何一段 JavaScript 代码都对应一个作用域链,作用域链中存放一系列对象,代码中声明的变量将作为对象的属性存放。

在 JavaScript 的顶层代码中,作用域链由一个全局对象组成;当定义一个函数时,它保存一个作用域链,作用域链上有两个对象,一个是函数对象,一个是全局对象。

每当一个函数被调用时,会创建一个活动对象(也叫上下文对象),函数中的局部变量将作为该对象的属性存放。

当需要使用一个变量时,将从作用域链中逐个查找对象的属性。比如:要使用变量 a,将先查找作用域中的第一个对象是否有属性 a,如果有就使用;如果没有就查找作用域链中

下一个对象的属性,以此类推。如果作用域链上没有任何一个对象含有属性 x,则认为这段代码的作用域链上不存在 x,将抛出引用错误异常。

当函数调用完成后,如果没有其他引用指向为此次调用所创建的上下文对象,该对象将被回收。

9.什么是“逻辑短路”?

逻辑短路是对于逻辑运算而言,是指,仅计算逻辑表达式中的一部分便能确定结果,而不对整个表达式进行计算的现象。
如:对于“&&”运算符,当第一个操作数为 false 时,将不会判断第二个操作数,因为此时无论第二个操作数为何,最后的运算结果一定是 false;

对于“||”运算符,当第一个操作数为 true 时,将不会判断第二个操作数,因为此时无论第二个操作数为何,最后的运算结果一定是 true。

10.谈谈 innerHTML、nodeValue 与 textContent 之间的区别

innerHTML 属性读取或设置节点起始和结束标签中的 HTML 内容;
nodeValue 属性读取或设置指定节点的文本内容,适用于文本类型的节点;
textContent 属性读取或设置指定节点的文本内容,对于元素节点而言,会返回所包含的所有子节点中的文本内容的组合。

11.简要描述 DOM 操作中查找元素的方式

通过 HTML 中的信息选取元素,比如:
a) getElementById()方法:根据元素的 id 属性值查询单个节点;
b) getElementsByTagName()方法:根据元素标签的名称查询点;
c) getElementsByName()方法:根据元素 name 属性的值查询点。

2、通过 CSS 类选取元素
a) getElementsByClassName(‘className’)方法:根据 class 名称选取元素;
b) querySelector(‘selector’)和querySelectorAll(‘selector’)方法:根据 CSS 选择器选取元素。
3、通过 document 对象选取,如 document.all、document.body 等;
4、通过节点遍历选取节点,如 parentNode、firstChild 等。

12.keep-alive的作用,什么时候使用?

  • keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。 在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。

    参数解释 include - 字符串或正则表达式,只有名称匹配的组件会被缓存 exclude - 字符串或正则表达式,任何名称匹配的组件都不会被缓存 include 和 exclude 的属性允许组件有条件地缓存。二者都可以用“,”分隔字符串、正则表达式、数组。当使用正则或者是数组时,要记得使用v-bind 。

    例如在创建时从 API 调用中引入数据的组件。你可能不希望每次动态切换这个组件进行渲染时都调用此 API。这时你可以将组件包含在 keep-alive 元素中。keep-alive 元素缓存该组件并从那里获取它,而不是每次都重新渲染它

13.什么是vue的计算属性?

在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。
好处:
①使得数据处理结构清晰;
②依赖于数据,数据更新,处理结果自动更新;
③计算属性内部this指向vm实例;
④在template调用时,直接写计算属性名即可;
⑤常用的是getter方法,获取数据,也可以使用set方法改变数据;
⑥相较于methods,不管依赖的数据变不变,methods都会重新计算,但是依赖数据不变的时候computed从缓存中获取,不会重新计算。

14.小程序开发性能优化

• 代码层面
拆分组件
图片压缩
减少不必要数据
避免频繁setData
使用webView组件开发
• 项目层面
拆分小程序
分包预加载
尽量升级版本库

15.Vue的父组件和子组件生命周期钩子执行顺序是什么?

1、加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
2、子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
3、父组件更新过程
父beforeUpdate->父updated
4、销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

16.From如何关闭自动完成

1、在IE的Internet选项菜单里的内容–自动完成里面设置
2、设置Form的autocomplete为"on"或者"off"来开启或者关闭自动完成功能(关闭整个表单(form)自动提示功能)
3、设置输入框(input)的autocomplete为"on"或者"off"来开启或者关闭该输入框的自动完成功能(关闭密码域的自动完成)

17.移动端的兼容问题

  1. ios下input为type=button属性disabled设置true,会出现样式文字和背景异常问题,怎么解决?
    (1) 使用opacity=1来解决
  2. 对非可点击元素如(label,span)监听click事件,部分ios版本下不会触发,怎么解决?
    (1) css增加了cursor:pointer就可以解决
  3. Input为fixed定位,在ios下input固定定位在顶部或者底部,在页面滚动一些距离后,点击input,位置会出现在中间位置。
    (1) 内容列表框也是fixed定位,这样不会出现fixed错位的问题
  4. 移动端字体小于12px使用四周边框或者背景色块,部分安卓文字偏上的bug问题。
    (1) 可以使用整体放大屏幕的dpr倍(width、height、font-size等等)再使用transform缩放,使用canvas在移动端会模糊也可以使用这种解决方案
  5. 在移动端图片上传,图片兼容低端机的问题
    (1) input加入属性accept=”image/*”multiple
  6. 在H5嵌入app中,ios如果出现垂直滚动条是,手指滑动页面滚动后,滚动很快停下来,有一种“滚动很吃力”的感觉
    (1) self.webView.scrollView.decelerationRate =UIScrollViewDecelerationRateNormal;对webview设置了更低的“减速率”
  7. 移动端click300ms延迟响应
    (1) 使用fastclickwindow.addEventListener( “load”, function() {FastClick.attach( document.body );}, false );
  8. 在安卓机上placeholder文字设置行高会偏上
    (1) Input有placeholder的情况下不要设置行高

18.重绘和重排

重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,是元素呈现新的外观。 触发重绘的条件:改变元素外观属性。如:color,background-color等。

重排(重构/回流/reflow):当渲染书中的一部分因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。

重绘和重排的关系:在回流的时候,浏览器回事渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。

所以,重排必定会引发重绘,当重绘不一定会引发重排。

触发重排的条件:任何页面布局和几何属性的改变都会触发重排,比如:
1、页面渲染初始化;(无法避免)
2、增加或删除可见的DOM元素;
3、元素位置的改变,或者使用动画;
4、元素尺寸的改变-------大小,外边距,边框;
5、浏览器窗口尺寸的变化(resize事件发生时)
6、填充内容的改变,比如文本的改变或者图片大小改变而引起的计算值宽度和高度的改变;
7、读取某些元素属性
(oddsetLeft/Top/Height/Width,clientTop/Left/Width/Height,scrollTop/Left/Width/Height,Width/Height,getComputedStyle(),currentStyle(IE))
8、重绘重排的代价:耗时,导致浏览器卡,慢。

优化:
1、浏览器自己的优化:浏览器会维护1个队列把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次的回流重绘。
2、我们要注意的优化:我么要减少重绘和重排就是要减少对渲染树的操作,则我们可以合并多次的DOM和样式的修改。并减少对style样式的请求。
(1) 直接改变元素的className
(2) display:none;先设置元素为display:none;然后进行页面布局等操作;设置完成后将元素设置为display:block;这样的话就只会引发两次重绘和重排;
(3) 使用cloneNode(true or false)和replaceChild技术,引发一次回流和重绘;
(4) 将需要多次重排的元素,position属性设置为absolute或fixed,元素脱离了文档流,它的变化不会影响到其他元素;
(5) 如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性的加入document;

猜你喜欢

转载自blog.csdn.net/TheDevil__/article/details/106405097