前端整理

427 阅读24分钟

CSS

1、盒模型

CSS的盒模型属于CSS的基础,被面试官问到的几率很大。无外乎 他们之间的区别,包含哪些东西

  • W3C 标准盒模型: 属性width,height只包含内容content,不包含border和padding
  • IE 盒模型: 属性width,height包含border和padding,指的是content+padding+border

可通过 box-sizing 进行设置。根据计算宽高的区域可分为:
1、 content-box (W3C 标准盒模型)
2、 border-box (IE 盒模型)
3、 padding-box
4、 margin-box (浏览器未实现)

2、BFC

块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响

  • 触发条件:

    • 根元素
    • position: absolute、fixed
    • display: inline-block、 table-cells、flex
    • float 元素
    • ovevflow !== visible
  • 规则:

    • 属于同一个 BFC 的两个相邻 Box 垂直排列
    • 属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
    • BFC 中子元素的 margin box 的左边, 与包含块 (BFC) border box的左边相接触 (子元素 absolute 除外)
    • BFC 的区域不会与 float 的元素区域重叠
    • 计算 BFC 的高度时,浮动子元素也参与计算
    • 文字层不会被浮动层覆盖,环绕于周围
  • 应用:

    • 阻止margin重叠
    • 可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)
    • 自适应两栏布局
    • 可以阻止元素被浮动元素覆盖

3、居中布局

  • 水平居中
    • 行内元素: text-align: center
    • 块级元素: margin: 0 auto
    • absolute + transform
    • flex + justify-content: center
  • 垂直居中
    • line-height: height
    • absolute + transform
    • flex + align-items: center
    • table
  • 水平垂直居中
    • absolute + transform
    • flex + justify-content + align-items

4、rem布局

rem官方定义『The font size of the root element』,即根元素的字体大小。

rem是一个相对的CSS单位,1rem等于html元素上font-size的大小。

(function () {
    var html = document.documentElement;
    function onWindowResize() {
        html.style.fontSize = html.getBoundingClientRect().width / 20 + 'px';
    }
    window.addEventListener('resize', onWindowResize);
    onWindowResize();
})();

5、flex布局

6、伪类和伪元素

伪类:

更多的定义的是状态。常见的伪类有:hover,:active,:focus,:visited,:link,:not,:first-child,:last-child等等。

伪元素:

不存在于DOM树中的虚拟元素,它们可以像正常的html元素一样定义css,但无法使用JavaScript获取。常见伪元素有 ::before,::after,::first-letter,::first-line等等。

7、opcity visibility display

8、CSS3新增加的特性

CSS新增的特性包含的有:

  • 选择器
  • 盒模型
  • 背景和边框
  • 文字特效
  • 2D/3D转换
  • 动画
  • 多列布局
  • 用户界面

JavaScript

1、原型 / 构造函数 / 实例

每个函数对象都会在其内部初始化一个属性,就是prototype(原型)
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念

  • 对象创建模式

    1. 对象字面量模式:
      使用{}创建对象, 同时指定属性/方法;
    var p = {
      name: 'Tom',
      age: 23,
      setName: function (name) {
        this.name = name
      }
    }
    console.log(p.name, p.age)
    p.setName('JACK')
    console.log(p.name, p.age)
    

    2. Object构造函数的模式:
    先创建空Object对象, 再动态添加属性/方法

    var p = new Object()
    p.name = 'Tom'
    p.age = 12
    p.setName = function (name) {
      this.name = name
    }
    p.setaAge = function (age) {
      this.age = age
    }
    console.log(p)
    

    以上两种方式的缺点显而易见,每次创建一个对象就需要手动设置它的每一个属性,造成大量代码重复

    1. 工厂模式:
      通过工厂函数动态创建对象并返回
    function createPerson(name, age, job){
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function(){
            console.log(this.name);
        };
        return o;
    }
    
    var person1 = createPerson('zzx', 22, 'Programmer');
    var person2 = createPerson('yzy', 20, 'Teacher');
    
    console.log(person1);
    //{ name: 'zzx', age: 20, job: 'Programmer', sayName: [Function] }
    

    这种模式解决了创建多个相似对象的问题,但是却不知道当前创建的对象是什么类型,即是Person还是Robot不能判断出来

    1. 构造函数模式:
    function Person(name, age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function(){
            console.log(this.name);
        };
    }
    
    function Robot(name, age){
        this.name = name;
        this.age = age;
    }
    
    var person1 = new Person('zzx', 22, 'Programmer');
    var person2 = new Person('yzy', 20, 'Teacher');
    
    console.log(person1 instanceof Person); //true
    console.log(person2 instanceof Robot); //false
    

    构造函数名开头大写借鉴了其他面向对象语言,是为了区别普通函数。任何一个函数不通过new操作符调用,就是一个普通函数:

    function Person(name, age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function(){
            console.log(this.name);
        };
    }
    
    Person('zzx', 22, 'Programmer');
    sayName(); //zzx
    

    构造函数仍然存在缺点,就是其中的每个方法例如sayName(),在每次实例化时都会自动重新创建一遍,产生不同的作用域链,因此即使是同名函数也是不相等的

    下面利用上述代码创建两个实例

    var person1 = new Person('zzx', 22, 'Programmer');
    var person2 = new Person('yzy', 20, 'Teacher');
    
    console.log(person1.sayName == person2.sayName); //flase
    

    5. 原型模式:
    使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法

    function Person(){
    }
    
    Person.prototype.name = 'zzx';
    Person.prototype.age = 22;
    Person.prototype.job = 'Programmer';
    Person.prototype.sayName = function(){
        console.log(this.name);
    }
    
    var person1 = new Person();
    person1.sayName(); //zzx
    var person2 = new Person();
    console.log(person1.sayName == person2.sayName); //true
    

    这里将sayName()方法和所有的属性直接添加到了Person 的prototype属性中,构造函数就成了空函数,但是也能调用构造函数创建新对象。新队先后的属性和方法是所有实例共享的,person1和person2访问的都是同一组属性和同一个sayName()函数。
    要理解原型模式工作原理,先要理解原型对象,原型对象

    上述方式每添加一个属性和方法都要输入Person.prototype,因此还有更简单的方式是以对象字面量形式创建

    function Person(){
    }
    
    Person.prototype = {
        name: 'zzx',
        age: '22',
        job: 'Programmer',
        sayName: function(){
            console.log(this.name);
        }
    };
    
    var person1 = new Person();
    console.log(person1 instanceof Person); //true
    console.log(person1.constrcutor == Person); //false
    

    但这里使用的语法本质上完全重写了默认的prototype对象(原型对象),因此本来会自动获得的constructor属性变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数,此时instanceof能返回正确的结果,但是constructor已经无法确定对象类型了

    如果constructor值真的很重要,可以通过下面这样特意将它设置回适当的值

    function Person(){
    }
    
    Person.prototype = {
        constructor: Person,
        name: 'zzx',
        age: '22',
        job: 'Programmer',
        sayName: function(){
            console.log(this.name);
        }
    };
    
    

    原型模式也有缺点,当其中包含引用类型值属性时会出现问题:

    function Person(){
    }
    
    Person.prototype = {
        constructor: Person,
        name: 'zzx',
        age: '22',
        job: 'Programmer',
        friends: ['wc', 'rt'],
        sayName: function(){
            console.log(this.name);
        }
    };
    
    var person1 = new Person();
    var person2 = new Person();
    
    person1.friends.push('lol');
    console.log(person1.friends); //[ 'wc', 'rt', 'lol' ]
    console.log(person2.friends); //[ 'wc', 'rt', 'lol' ]
    
    

    由于数组存在于Person.prototype中,当向数组中添加了一个字符串时,所有的实例都会共享这个数组(基本值可以被实例屏蔽)

    1. 组合使用构造函数模式和原型模式:
      创建自定义类型的常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
    function Person(name, age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.friends = ['wc', 'rt'];
    }
    
    Person.prototype = {
        constructor: Person,
        sayName: function(){
            console.log(this.name);
        }
    };
    
    
    1. 动态原型模式:
      这里只在 sayName()方法不存在的情况下,才会将它添加到原型中
    function Person(name, age, job){
        this.name = name;
        this.age = age;
        this.job = job;
    
        if(typeof this.sayName != "function"){
            Person.prototype.sayName = function(){
                console.log(this.name);
            };
        }
    }
    
    var person1 = new Person('zzx', 22, 'Programmer');
    person1.sayName();
    
    
    1. 寄生构造函数模式:
      这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数
    function Person(name, age, job){
        var o = new Object()
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function(){
            console.log(this.name);
        }
        return o;
    }
    
    var person1 = new Person('zzx', 22, 'Programmer');
    person1.sayName();
    
    
  • 对象实现继承的几种方法

    首先定义一个父类:

      //构造函数
      function Animal(name) {
      	this.name = name || 'Animal';
      	this.sleep = function() {
      		console.log(this.name + '正在睡觉!');
      	};
      }
      //原型上面的方法:
      Animal.prototype.eat = function(food) {
      	console.log(this.name + '正在吃:' + food);
      }
    
    1. 原型链继承:
    //核心:将父类的实例作为子类的原型
    function Dog() {
    
    }
    Dog.prototype = new Animal();  //将Animal的实例挂载到了Dog的原型链上
    //或:
    //Dog.prototype = Object.create(Animal.prototype)
    Dog.prototype.name = 'dog';
    
    var dog = new Dog();
    console.log(dog.name);		//dog
    dog.eat('bone');		//dog正在吃:bone
    dog.sleep();		//dog正在睡觉!
    console.log(dog instanceof Animal);		//true
    console.log(dog instanceof Dog);		//true
    

    特点:

    • 非纯粹的继承关系,实例是子类的实例,也是父类的实例
    • 父类新增原型方法/原型属性,子类都能访问的到
    • 简单

    缺点:

    • 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

    • 无法实现继承多个

    • 来自原型对象的所有属性被所有实例共享

    • 创建子类实例时,无法向父类构造函数传参

    1. 构造继承:
    //核心:使用父类的构造函数增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
    function Cat(name) {
    	Animal.call(this);
    	this.name = name || 'Tom';
    }
    
    var cat = new Cat();
    console.log(cat.name);		//Tom
    cat.sleep();		//Tom正在睡觉!
    console.log(cat instanceof Animal);		//false
    console.log(cat instanceof Cat);		//true
    

    特点:

    • 创建子类实例时,可以向父类传递参数
    • 可以实现多继承(call多个父类对象)

    缺点:

    • 实例并不是父类的实例,只是子类的实例

    • 只能继承父类的实例属性和方法,不能继承原型属性/方法

    • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

    1. 实例继承:
    //核心:为父类实例添加新特性,作为子类实例返回
    function Cat(name) {
    	var instance = new Animal();
    	instance.name = name || 'Tom';
    	return instance;
    }
    
    var cat = new Cat();
    console.log(cat.name);		//Tom
    cat.sleep();			//Tom正在睡觉!
    console.log(cat instanceof Animal);		//true
    console.log(cat instanceof Cat);		//false
    

    特点:

    • 不限制调用方式,不管是new子类()还是子类(),返回的对象都具有相同的效果

    缺点:

    • 实例是父类的实例,不是子类的实例

    • 不支持多继承

    1. 拷贝继承:
    function Cat(name){
    	var animal = new Animal();
    	for(let i in animal) {
    		Cat.prototype[i] = animal[i];
    	}
    	Cat.prototype.name = name || 'Tom';
    }
    
    var cat = new Cat();
    console.log(cat.name);	//Tom
    cat.sleep();	//Tom正在睡觉!
    console.log(cat instanceof Animal); 	// false
    console.log(cat instanceof Cat);	 // true
    

    特点:

    • 支持多继承

    缺点:

    • 效率极低,内存占用高(因为要拷贝父类的属性)

    • 无法获取父类不可枚举的方法(for in不能访问到的)

    1. 组合继承:
    //核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
    function Cat(name) {
    	Animal.call(this);
    	this.name = name || 'Tom';
    }
    Cat.prototype = new Animal();
    Cat.prototype.constructor = Cat;
    
    var cat = new Cat();
    console.log(cat.name);	//Tom
    cat.sleep();		//Tom正在睡觉
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); // true
    

    特点:

    • 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继- 承原型属性/方法
    • 既是子类的实例,也是父类的实例
    • 不存在引用属性共享问题
    • 函数可复用
    • 可传参

    缺点:

    • 调用了俩次构造函数,生成了俩份实例(子类实例将子类原型上的那份屏蔽了)

    1. 寄生组合继承:
    //核心:通过寄生方式,砍掉父类的实例属性,这样,在调用俩次父类的构造的时候,就不会初始化俩次实例方法/属性,避免了组合继承的缺点。
    function Cat(name) {
    	Animal.call(this);
    	this.name = name || 'Tom';
    }
    (function() {
    	var Super = function() {};  //创建一个没有实例的方法类。
    	Super.prototype = Animal.prototype;
    	Cat.prototype = new Super();  //将实例作为子类的原型。
    })();
    
    let cat = new Cat();
    console.log(cat.name);		//Tom
    cat.sleep();		//Tom正在睡觉
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); //true
    
    Cat.prototype.constructor = Cat;	//修复构造函数
    
    // 简而言之,可以通过一下操作去实现
    function Father1(){
        this.father_Property1 = ["父类属性01","父类属性02","父类属性03"]
        this.father_Property2 = "父类属性2"
    }
    function Son1(){
        Father1.call(this);//或apply
    }
    Son1.prototype = Object.create(Father1.prototype);
    Son1.prototype.constructor = Son1;
    

    特点:

    • 基本上是完美的

    缺点:

    • 实现起来较为复杂

2、完全搞懂js 中的new()到底做了什么?

new 生成对象的方式经历了什么?

  1. 创建一个空的对象
  2. 这个新对象的 proto 属性指向原函数的 prototype 属性
  3. 将这个新对象绑定到此函数的 this 上
  4. 返回新对象,如果这个函数没有返回其他对象
function _new(constructor, ...arg) {
    // ① 创建一个新的空对象 obj
    const obj = {};
    // ② 将新对象的的原型指向当前函数的原型
    obj.__proto__ = constructor.prototype;
    // ③ 新创建的对象绑定到当前this上
    const result = constructor.apply(obj, arg); 
    // ④ 如果没有返回其他对象,就返回 obj,否则返回其他对象
    return typeof result === 'object' ? result : obj;
}
function Foo(name) {
    this.name = name;
}
var luckyStar = _new(Foo, 'luckyStar');
luckyStar.name; //luckyStar

3、Object.create()和new生成对象哪里不一样

4、闭包

函数执行,形成一个私有的作用域,保护里边的私有变量不受外界的干扰,除了保护私有变量外,还可以保存一些内容,这样的模式叫做闭包

  • 作用
    • 保护的应用
    • 保存的应用

5、回调

6、this

  • this的几种指向

    • 对象调用,this 指向该对象
    • 直接调用的函数,this指向的是全局 window对象
    • 通过 new的方式,this永远指向新创建的对象
    • 箭头函数的 this与声明所在的上下文相同
  • 如何改变 this 的指向
    我们可以通过调用函数的 call、apply、bind 来改变 this的指向。

      var obj = {
          name:'小鹿',
          age:'22',
          adress:'小鹿动画学编程'
      }
      
      function print(){
          console.log(this);       // 打印 this 的指向
          console.log(arguments);  // 打印传递的参数
      }
      
      // 通过 call 改变 this 指向
      print.call(obj,1,2,3);   
      
      // 通过 apply 改变 this 指向
      print.apply(obj,[1,2,3]);
      
      // 通过 bind 改变 this 的指向
      let fn = print.bind(obj,1,2,3);
      fn();
      
    

7、垃圾回收机制

不再用到的内存,没有及时释放,就叫做内存泄漏

内存泄漏是指我们已经无法再通过js代码来引用到某个对象,但垃圾回收器却认为这个对象还在被引用,因此在回收的时候不会释放它
导致了分配的这块内存永远也无法被释放出来。如果这样的情况越来越多,会导致内存不够用而系统崩溃

  • 两种垃圾回收策略
    • 标记清除法
    • 引用计数法

标记清除法

垃圾回收器会在运行的时候,会给存储在内存中的所有变量都加上标记,然后它会去掉环境中变量以及被环境中的变量引用的变量的标记。剩下的就视为即将要删除的变量,原因是在环境中无法访问到这些变量了。最后垃圾回收器完成内存清除操作

它的实现原理就是通过判断一个变量是否在执行环境中被引用,来进行标记删除

引用计数法

引用计数的垃圾收集策略不常用,引用计数的最基本含义就是跟踪记录每个值被引用的次数。

当声明变量并将一个引用类型的值赋值给该变量时,则这个值的引用次数加 1,同一值被赋予另一个变量,该值的引用计数加 1 。当引用该值的变量被另一个值所取代,则引用计数减 1,当计数为 0 的时候,说明无法在访问这个值了,所有系统将会收回该值所占用的内存空间。

8、宏任务与微任务

9、requestAnimationFrame

10、防抖节流

防抖:

所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

  • search搜索联想,用户在不断输入值时,用防抖来节约请求资源
  • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
function debounce (fuc, delay) {
  let timer
  return function () {
    let that = this
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fuc.call(this, arguments)
    }, delay)
  }
}

节流:

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
function throttle(fuc, limit) {
  let preTime = 0
  return function () {
    let that = this
    let nowTime = Date.now()
    if (nowTime - preTime > limit) {
      fuc.apply(that, arguments)
      preTime = nowTime
    }
  }
}

function throttle(func, wait) {
  let timeout;
  return function (...args) {
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null;
        func.apply(this, args);
      }, wait);
    }
  };
}

10、前端跨域

广义上的域大概是单指一个网站域名,但在专业领域,我们把它叫源,所谓同源是指:

  1. 协议相同
  2. 域名相同
  3. 端口相同

三者同时成立才能叫同源。浏览器的同源策略从它诞生的那一刻就出现了,具体是指:

从域名A下的一个页面(一般是通过ajax请求)获取域名B下的一个资源,是不被浏览器允许的。

不被允许的意思是,浏览器还是会发出这个请求,但是它会拦截响应内容,如果发现响应header中"Access-Control-Allow-Origin"设置的允许访问的源没有包含当前源,则拒绝将数据返回给当前源。

11、jsonp原理

12、call、apply、bind

13、js阻止默认行为和阻止冒泡

  • 阻止默认行为
//阻止默认行为
// JavaScript
document.getElementById('btn').addEventListener('click', function ( event ) {
    event = event || window.event;
    if ( event.preventDefault ) {
        // W3C
        event.preventDefault();
    }
    else {
        // IE
        event.returnValue = false;
    }
}, false);
 
// jQuery
$('#btn').on('click', function ( event ) {
    event.preventDefault();
});
  • 阻止冒泡

// JavaScript
document.getElementById('btn').addEventListener('click', function ( event ) {
    event = event || window.event;
    if ( event.stopPropagation ) {
        // W3C
        event.stopPropagation();
    }
    else {
        // IE
        event.cancelBubble = true;
    }
}, false);
 
// jQuery
$('#btn').on('click', function ( event ) {
    event.stopPropagation();
})

14、箭头函数

  • 引用资料
  • 使用注意点
    • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
    • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
    • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替
    • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数

箭头函数、没有prototype、没有自己的this指向、不可以使用arguments、自然不可以new

let funA = (a1, a2) => {
    console.log(a1, a2)
}
let funB = function(b1, b2) {
    console.log(b1, b2)
}
console.dir(funA)
console.dir(funB)

箭头函数无法使用arguments,而普通函数可以使用arguments

如果要使用类似于arguments获取参数,可以使用rest参数代替

let funC = (...arg) => { 
    console.log(...arg) 
}; 
funC(1,2)

15、JS运行机制

16、require与import的区别

  • 引用资料

  • CommonJS(require)

    • 对基本类型,属于复制;a = require(b),b会被a缓存
    • 对于复杂数据类型,属于浅拷贝,浅拷贝的话就存在一个问题,修改a的话b也会被修改
    • require是运行时调用,所以require理论上可以运用在代码的任何地方
  • ES6模块(import)

    • mport属于【动态只读引用】,即import a from b a是只读变量,不论基本数据类型还是复杂数据类型
    • 对于动态来说, 原始值发生变化,import加载的值也会发生变化。不论基本类型还是复杂类型 import('lodash').then(_ => { // Do something with lodash (a.k.a '_')... })
    • import是编译时调用,所以必须放在文件开头

17、关于CSS与JS引用的讨论

  • 引用资料

    从上面两个流程图我们可以看出来,浏览器渲染的流程如下:

    1、HTML解析文件,生成DOM Tree,解析CSS文件生成CSSOM Tree
    2、将Dom Tree和CSSOM Tree结合,生成Render Tree(渲染树)
    3、根据Render Tree渲染绘制,将像素渲染到屏幕上

    css放在body标签尾部时:
    DOMTree构建完成之后便开始构建RenderTree, 并计算布局渲染网页, 等加载解析完css之后, 开始构建CSSOMTree, 并和DOMTree重新构建RenderTree, 重新计算布局渲染网页

    css放在head标签中时:
    先加载css, 之后解析css构建CSSOMTree, 于此同时构建DOMTree, CSSOMTree和DOMTree都构建完毕之后开始构建RenderTree, 计算布局渲染网页

    css放在head标签中比css放在body标签尾部少了一次构建RenderTree, 一次计算布局和一次渲染网页, 因此性能会更好; 并且css放在body标签尾部时会在网页中短暂出现"裸奔"的HTML, 这不利于用户体验

    浏览器内核可以分成两部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎

    浏览器的单线程导致解析渲染和js的执行不能并存,也就是说当 HTML 解析器遇到一个 script 标签时,它会暂停渲染过程,将控制权交给 JS 引擎

    如果js写在最前面,如果不做特殊处理,除了能提供依赖以外和做一些预处理之外,连dom节点都无法捕捉,导致功能受到了很大的限制

  • async与defer区别:

    • 相同点: 都是异步加载,不阻塞页面渲染;都可以只使用属性名,不定义属性值;都只对外部脚本适用(IE7之前针对嵌入式脚本使用defer,IE8+只支持外部脚本)

    • 不同点:async 会在加载完成后立即执行,因此它是无序执行,但一定会在window的onload事件之前执行,DOMContentLoaded事件前后不确定

      defer 会等到整个页面解析完后再执行,一般会按照顺序执行,会先于DOMContentLoaded事件触发前执行

18、垃圾回收和内存泄漏

19、JS设计模式

  1. 单例模式
  2. 策略模式
  3. 代理模式
  4. 迭代器模式
  5. 发布—订阅模式
  6. 命令模式
  7. 组合模式
  8. 模板方法模式
  9. 享元模式
  10. 职责链模式
  11. 中介者模式
  12. 装饰者模式
  13. 状态模式
  14. 适配器模式
  15. 外观模式

20、常见兼容性问题

21、图片懒加载

22、图片上传

23、数据类型

VUE

1、vue-router两种路由模式

2、vue双向绑定

3、keep-alive理解

4、vue-router 保留滚动位置

5、Vue 的父组件和子组件生命周期钩子函数执行顺序

  • 加载渲染过程
    父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

  • 子组件更新过程
    父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

  • 父组件更新过程
    父 beforeUpdate -> 父 updated

  • 销毁过程
    父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

6、vue为什么使用this.$set

受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

var vm = new Vue({
  data:{
    a:1
  }
})

// `vm.a` 是响应式的

vm.b = 2
// `vm.b` 是非响应式的

对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。

Vue.set(vm.someObject, 'b', 2)

您还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:

this.$set(this.someObject,'b',2)

7、nextTick

vue的文档上有这样一句话

将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上

Vue 数据更新之后,Dom 不会立即发生变化,而是按照一定的策略进行 Dom 的更新。

nextTick 是在下次 Dom 更新循环之后执行延迟回调,在修改数据之后使用nextTick,可以在回调中获取更新的Dom

## 事例:
<template>
  <div>
    <div ref="test">{{test}}</div>
    <button @click="handleClick">tet</button>
  </div>
</template>
export default {
    data () {
        return {
            test: 'begin'
        };
    },
    methods () {
        handleClick () {
            this.test = 'end';
            console.log(this.$refs.test.innerText); //打印“begin”
        }
    }
}

注意:访问页面的Dom元素获取文本内容,会打印出begin,而不是end

从事件循环的角度上理解:
→ 修改数据
→ 同步任务都在主线程上执行,形成一个执行栈,此时还未涉及DOM
→ Vue开启一个异步队列,并缓冲在此事件循环中发生的所有数据变化。如果同一个watcher被多次触发,只会被推入队列中一次
→ 同步任务执行完毕,开始执行异步watcher队列的任务,更新DOM。Vue在内部尝试对异步队列使用原生的Promise.then和MessageChannel方法
→ Vue.nextTick()的回调函数

8、路由懒加载和组件懒加载

9、vue组件通信

工具化

1、Gulp和webpack的区别

2、webpack4

前端工程

1、前端缓存

2、三次握手和四次挥手

3、网络攻击XSS和CSRF

3、Http状态码

4、输入url到展示的过程

  • 引用资料
  • DNS解析
  • TCP三次握手
  • 发送请求,分析url,设置请求头
  • 服务器返回请求的文件(html)
  • 浏览器渲染
    • 解析html文件,生成dom树
    • 解析css文件,生成style树
    • 结合dom树和style树,生成渲染树(render tree)
    • layout布局渲染
    • GPU像素绘制页面

5、 cookie、sessionStorage和localStorage

两种储存方式的对比

特性cookiesessionStoragelocalStorage
生命周期在设置的maxAge期间内,缓存有效在同一个会话中的页面才能访问,当前页面关闭数据也随之销毁同源可以读取并修改localStorage数据,除非主动删除数据,否则数据是永远不会过期的
存放数据大小4K左右一般5M或者更大一般5M或者更大
数据通信会携带在http请求头中,储存过多会带来性能问题只存在客户端只存在客户端

他们储存的对象类型均为字符串类型

const info = {
    name: 'Lee',
    age: 20,
    id: '001'
};
sessionStorage.setItem('key', JSON.stringify(info));
localStorage.setItem('key', JSON.stringify(info));

localStorage 跨域:

关于localStorage 跨域通信的问题,可以通过使用iframe与postMessage实现

假设:
A网站:www.a.jwt.com/login.php
B网站:www.b.jwt.com/callback.ph…
通过A网站 获取 b网站的 localStorage 信息

## A网站代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
    <iframe src="http://www.b.jwt.com/callback.php" frameborder="0"></iframe>
    <script>

        window.onload = function(){
            //在页面加载完成后主页面向iframe发送请求
            window.frames[0].postMessage('99','http://www.b.jwt.com');
        }

        // 主页面监听message事件,
        window.addEventListener('message', function(e){
            var data = e.data;
            console.log(data)
        }, false);
    </script>
</body>
</html>

## B网站代码
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	
</body>
</html> 
 <script>
    //iframe接收消息,并把当前颜色发送给主页面  
    window.addEventListener('message', function(e) {  
        if (e.source != window.parent)   
            return;  
        // console.log(e.data)
        localStorage.getItem(e.data);
        obj = {};
        obj[e.data] = localStorage.getItem(e.data)
        window.parent.postMessage(obj, '*');  //* 代表所有网站
    }, false);
</script>

6、 cookie和session

7、浏览器渲染

8、浏览器单线程EventLoop

9、回流和重绘