前端知识点

JS

VO:变量对象

AO:活动对象

  • defer & async

    常规script脚本浏览器会立即加载并执行,异步加载使用asyncdefer
    二者区别在于aysnc为无序,defer会异步根据脚本位置先后依次加载执行

Vue

生命周期

​ 从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

  1. beforeCreate
    • 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调
  2. created
    • 实例已经创建完成,数据观测(data observer) 和 event/watcher 事件配置完成,但真实dom还没有生成,$el 还不可用。
    • 调用数据,调用方法,调用异步函数
  3. beforeMount
    • 相关的render函数(模板)首次被调用
  4. mounted
    • 挂载完成, $el 可用,dom渲染完成
  5. beforeUpdate
    • 数据更新时调用,发生在学你dom重新渲染和打补丁之前,可以在这个钩子中进一步地更改状态,并不会触发附加的重渲染过程。当我们更改Vue的数据,都会触发改钩子
  6. updated
    • 组件dom 已经更新,可以执行依赖于dom的操作,然而应该避免在此更改状态,可能会导致更新无限循环
  7. beforeDestroy
    • 实例销毁之前调用,在这一步,实例仍然完全可用
  8. destroyed
    • 实例销毁后调用

Vue生命周期在真实场景下的业务应用

created: 进行ajax请求异步数据的获取、初始化数据

mounted: 挂载元素dom节点的获取

nextTick: 针对单一事件更新数据后立即操作dom

updated: 任何数据的更新,如果要做统一的业务逻辑处理

watch: 监听数据变化,并做相应的处理

插槽

  • 缩写 v-slot:header => #header,#default=”{user}”

  • 说明

    插槽就是子组件中提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如HTML,组件等,填充的内容会替换子组件的<slot></slot>标签
    复制代码
  • 场景

    在构建页面过程中一般会把用的比较多的公共的部分抽取出来作为一个单独的组件,但在实际开发过程中这个组件并不能完全满足需求,这个时候我们就需要用到插槽来分发内容
    复制代码
  • 作用

    可以拓展组件,去更好的复用组件和对其做定制化处理
    复制代码
  • 编译作用域

    父级 模板里所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的
    复制代码
  • 注意:v-slot只能添加在<template>上 ,默认插槽除外

    <current-user v-slot:default="slotProps">
      {{ slotProps.user.firstName }}
    </current-user>
    // 简写
    <current-user v-slot="slotProps">
      {{ slotProps.user.firstName }}
    </current-user>
    
    // 默认插槽的缩写语法不能和具名插槽混用,因为会导致作用域不明确
    <!-- 无效,会导致警告 -->
    <current-user v-slot="slotProps">
      {{ slotProps.user.firstName }}
      <template v-slot:other="otherSlotProps">
        slotProps is NOT available here
      </template>
    </current-user>
    
    // 只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法:
     <current-user>
      <template v-slot:default="slotProps">
        {{ slotProps.user.firstName }}
      </template>
    
      <template v-slot:other="otherSlotProps">
        ...
      </template>
    </current-user>   
    复制代码
  • 分类

    默认插槽
    // parent
    // 无插槽内容
    <submit-button></submit-button>
    
    // 有插槽内容
    <submit-button>Save</submit-button>
    复制代码
    // <submit-button>
    <button type="submit">
      <slot>Submit</slot> // 如果父组件没有提供任何插槽内容,显示Submit
    </button>
    
    // 显示
    <button type="submit">
      <slot>Save</slot> // 显示Save
    </button>
    复制代码
    具名插槽
    // child  <myslot>
    <template>
        <div class="container">
          <header>
            <!-- 我们希望把页头放这里 -->
          </header>
          <main>
            <!-- 我们希望把主要内容放这里 -->
          </main>
          <footer>
            <!-- 我们希望把页脚放这里 -->
          </footer>
        </div>
    </template>
    
    // 修改后
    <template>
      <div class="container">
        <header>
        <slot name="header"></slot>
        </header>
      <main>
          <slot></slot>
        </main>
        <footer>
          <slot name="footer"></slot>
        </footer>
      </div>
    </template>
    复制代码
    // parent
    <template>
      <myslot>
        <div>大家好我是父组件</div>
        <template v-slot:header>
          <h1>Here might be a page title</h1>
        </template>
    
        <p>A paragraph for the main content.</p>
        <p>And another one.</p>
    
        <template v-slot:footer>
          <p>Here's footer info</p>
        </template>
      </myslot>
    </template>
    
    <script>
      import myslot from './myslot';
      export default {
        components: {
          myslot
        }
      }
    </script>
    复制代码
    作用域插槽(插槽内容能够访问子组件中才有的数据)
    // child current-user
    <span>
        <slot v-bind:user="user">
          {{ user.lastName }}
        </slot>
    </span>
    
    // parent
    <current-user>
      <template v-slot:default="slotProps">
        {{ slotProps.user.firstName }}
      </template>
    </current-user>
    // 解构
    <current-user>
    
    <template v-slot:default="{user}">
      {{ user.firstName }}
    </template>
    </current-user>
    
      // 重命名
      <current-user>
      <template v-slot:default="{user: person}">
        {{ person.firstName }}
      </template>
      </current-user>
    
      // 自定义后备内容
      <current-user>
      <template v-slot:default="{user={firstName: 'hui'}}">
        {{ person.firstName }}
      </template>
      </current-user>
    复制代码
    动态插槽名
    <base-layout>
       <template v-slot:[dynamicSlotName]>
          ...
       </template>
     </base-layout>
    复制代码

插件

添加全局方法或者property。如:vue-custom-element

添加全局资源:指令/过滤器/过渡等。如:vue-touch

通过全局 mixin 来添加一些组件选项。如vue-router

添加全局实例方法,通过把它们添加到 config.globalProperties 上实现

一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
复制代码
使用插件

通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成,Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。

3.0 新特性

组合式API,2.0为选项式API
对原data中的属性,使用ref包装为响应式对象,对原prop的属性,使用toRefs包装为响应式对象
props

​ 因为 props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。

context => { attrs, slots, emit}:

context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。

渲染机制和优化

虚拟 DOM 是轻量级的 JavaScript 对象,由渲染函数创建。它包含三个参数:元素,具有数据、prop、attr 等的对象,以及一个数组。数组是我们传递子级的地方,子级也具有所有这些参数,然后它们也可以具有子级,依此类推,直到我们构建完整的元素树为止。
复制代码

Object, Set和Map的区别

  1. key的不同,在Object 中, key必须是简单数据类型(整数,字符串或者是symbol),而在Map中则可以是JavaScript 支持的所有类型数据,也就是说可以用一个Object 或 Function 来当做一个Map元素的key

  2. 使用

    Obejct
    • object 可以通过Object.keys,Object.value,Object.entries,for…in…等遍历拿到键或者值
    • 可以用过 “a” in obj 来判断obj中是否有a属性
    Map
    • Map可以通过this.keys,this.values,this.entries,for…of…等遍历拿到键或者值
    • 常用方法map.set(key,value)、map.get(key)、map.has(key)
    • Map 在存储大量元素的时候性能表现更好
    • var map = new Map([[1, 2], [2, 3]]); // map = {1 => 2, 2 => 3}
    Set
    • Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构

    • Set和Map主要的应用场景在于数组去重数据存储

    • 常用方法

        // size:返回集合所包含元素的数量
        // Set的方法:
        // 操作方法
        add(value)//向集合添加一个新的项
        delete(value)//从集合中移除一个值
        has(value)//如果值在集合中存在,返回true,否则false
        clear()// 移除集合里所有的项
        遍历方法
        keys()//返回一个包含集合中所有键的数组
        values()//返回一个包含集合中所有值的数组
        entries//返回一个包含集合中所有键值对的数组(感觉没什么用就不实现了)
        forEach()//用于对集合成员执行某种操作,没有返回值
    复制代码
不同
  • JSON 直接支持 Object,但不支持 Map

  • Map 是纯粹的 hash, 而 Object 还存在一些其他内在逻辑,所以在执行 delete 的时候会有性能问题。所以写入删除密集的情况应该使用 Map。

  • Map 会按照插入顺序保持元素的顺序(FIFO 原则),而Object做不到

  • Map 自身有 size 属性,可以自己维持 size 的变化。Object 则需要借助 Object.keys() 来计算

console.log(Object.keys(obj).length);
复制代码
  • 如何确定一个类型是不是支持迭代呢? 可以使用以下方法:
console.log(typeof obj[Symbol.iterator]); // undefined
console.log(typeof map[Symbol.iterator]); // function
复制代码

日志追踪实用技巧

  • try…catch…这种方式适合捕获可预见的错误,无法捕获异步错误

  • window.onerror 可以捕获到语法错误,但无法捕获到网络异常的错误。

  • window.addEventListener(“error”, fun, true) 折中方式虽然可以捕获到网络请求的异常,但是无法判断HTTP的状态是404,还是其它如500等,还需要配合服务端日志才进行排查分析才可以

  • unhandledrejection:可以捕获到promise异常,也是通过window.addEventListener(‘unhandledrejection’, fun, true)方式

  • 可以配合配合项目使用的前端框架的错误处理方式来收集,比如VUE的异常处理方法errorHandler,可以通过重写vue.prototype.errorHandler 方法来达到手机框架的一些异常

    var defaults = {
        ua: window.navigator.userAgent,
        browser: '',
        os: '',
        osVersion: '',
        errUrl: window.location.href,
        msg: '', // 错误的具体信息
        url: '', // 错误所在的url
        line: '', // 错误所在的行
        col: '', // 错误所在的列
        error: '' // 具体的error对象
      }
    复制代码

Promise

  • promise 是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)

  • 多个异步等待合并便于解决

  • 3种状态:pending,fulfilled,rejected

  • 常用方法:Promise.all() Promise.race Promise.resolve Promise.reject

  • Promise.all 和 Promise.race 区别

    let p = Promise.all([p1,p2,p3])

    all():只有p1, p2, p3 的状态都变成fulfilled, p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组传递给p的回调函数

    ​ 只要有一个rejected,p的状态就变成rejected,此时第一个被reject的实例返回值会传递给p的回调函数

    race():只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。

// 手写Promise

复制代码

async…await

async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。async/await 是建立在 promise 的基础上

async/await 让异步代码看起来、表现起来更像同步代码。这正是其威力所在。

闭包

【函数】和【函数内部能访问到的变量】(也叫环境)的总和,就是一个闭包

经常使用的场景中return 的作用是使用闭包,让别人可以间接访问

作用:常常用来【间接访问一个变量】。换句好说,【隐藏一个变量】

// 案列一:
function a(){
    let a = "sd";
    function b(){
        console.log(a);
    }
}
// 案列2 return 作用是让别人可以间接访问到a变量
// 闭包:b可以读取a中的变量,只要把b作为返回值,就可以在a外读取a内部变
// 原因:a是b的父函数,b被赋给了一个全局变量,a始终存在内存中,b的存在依赖a,因此a也始终存在内存中,不会在调用结束后,被垃圾回收机制回收。
function a(){
    let a = "sd";
    return function b(){
        console.log(a);
    }
}


复制代码

场景:

  1. 使用闭包代替全局变量

  2. 函数外或在其他函数中访问某一函数内部的参数

  3. 在函数执行之前为要执行的函数提供具体参数

  4. 在函数执行之前为函数提供只有在函数执行或引用时才能知道的具体参数

  5. 为节点循环绑定click事件,在事件函数中使用当次循环的值或节点,而不是最后一次循环的值或节点

  6. 暂停执行

  7. 包装相关功能

  // 1,2
  //闭包,test2是局部变量,这是闭包的目的
  //我们经常在小范围使用全局变量,这个时候就可以使用闭包来代替。
  (function(){
    var test2=222;
    function outer(){
        alert(test2);
    }
    function test(){
        alert("测试闭包:"+test2);
    }
    outer(); //222
    test(); //测试闭包:222
  })();

  alert(test2); //未定义,这里就访问不到test2


  // 3
  //正确做法,使用闭包
  function f3(obj){return function(){alert(obj)}}
  var test2=f3('222');//返回f3的内部函数的引用
  setTimeout(test2,500);//正确,222
  document.getElementById("hello").onclick=test2;//正确,222
  document.getElementById("hello").attachEvent("onclick",test2);//正确,222


  //原生的setTimeout传递的第一个函数不能带参数
  setTimeout(function(param){
      alert(param)
  },1000)


  //通过闭包可以实现传参效果
  function func(param){
      return function(){
          alert(param)
      }
  }
  var f1 = func(1);
  setTimeout(f1,1000);



  //用闭包定义能访问私有函数和私有变量的公有函数。
  var counter = (function(){
      var privateCounter = 0; //私有变量
      function change(val){
          privateCounter += val;
      }
      return {
          increment:function(){   //三个闭包共享一个词法环境
              change(1);
          },
          decrement:function(){
              change(-1);
          },
          value:function(){
              return privateCounter;
          }
      };
  })();


  // 6

  function sleep(obj,iMinSecond){
  if (window.eventList==null) window.eventList=new Array(); 
      var ind=-1; 
      for (var i=0;i<window.eventList.length;i++){ 
          if (window.eventList[i]==null) { 
              window.eventList[i]=obj; 
              ind=i; 
              break; 
          } 
      } 

      if (ind==-1){ 
          ind=window.eventList.length; 
          window.eventList[ind]=obj; 
      } 

      setTimeout("goon(" + ind + ")",iMinSecond); 
  } 

  goo(){}
  function test(){
    sleep(this,3000);//调用暂停函数
  }

复制代码

原型,原型链

  • 所有函数都有 prototype(原型)属性,所有引用类型都有 __proto__(隐式原型)属性,都是一个普通对象

  • 引用类型的__proto__指向它的构造函数的prototype

    let Person = function(){}
    let person = new Person();
    person.__proto__ = Person.prototype
    person.__proto__.constructor = Person;
    Person.prototype.constructor = Person;
复制代码

当访问对象上的某个属性,会先在对象本身上查找,如果没有找到会继续在它的__proto__(隐式原型)上查找,即查找它的构造函数原型,如果还有找到,则继续在原型的__proto__上查到,直到Object.prototype.__proto__ (为null)停止。这样一层一层的向上查找就形成了一个链式结构(原型链)。另一面证明了万物皆对象

访问链路为:

这里写图片描述

作用:

  • 继承,减少了内存占用
  • prototype用来实现基于原型的继承与属性的共享
  • 避免了代码冗余,公用的属性和方法可以提取放在原型中,这样通过改构造函数实例化的所有对象都可以使用该对象的构造函数中的属性和方法

场景:

  • 有重复的代码和属性

特点:

  • 就近原则,优先使用离自己近的
  • 引用类型,当我们使用或者修改原型链上的值时。其实使用的都是同一个值
  • 所有函数的默认原型都是Object的实例

第一种

funtion People(name, age){
    this.name = name;
    this.age = age;
}
People.prototype.printInfo = function(){
    console.log(`name: ${this.name}, age: ${this.age}`)
}
let people = new People("小明", 16);
people.printInfo(); // name: 小明,age: 16
复制代码

第二种

const People = {
    name:"",
    age:"",
    printInfo: function(){
        console.log(`name: ${name}, age: ${age}`)
    }
}

let people = Object.create(People);
people.name = "小明";
people.age = 16;
people.printInfo(); // name: 小明,age: 16

复制代码

第三种

  • 弊端:instanceof 不能判断类
let Botany = {
    createNew: function(color) {
        let obj = {}
        obj.color = color;
        obj.printColor = function() {
            console.log(this.color);
        }
        return obj
    }
}

let cactus = Botany.createNew("green");
cactus.printColor();

// 继承
let A = {
        createNew: function() {
            let obj = Botany.createNew();
            // ...do something
            return obj;
        }
    }
    // 共享
let B = {
    isBotany: true,
    createNew: function() {}
}
复制代码

第四种 class 关键字

class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

var b = new Point(); // 必须使用new 关键字,否则会报错

Object.keys(Point.prototype) // []
Object.getOwnPropertyNames(Point.prototype); // ["constructor", "toString"]

//上面代码中,toString()方法是Point类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。

var Point = function (x, y) {
  // ...
};

Point.prototype.toString = function () {
  // ...
};

Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
复制代码

注意事项

  • 私有属性的解决方案

    // 第一种 _bar()方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法
    class Widget {
    
      // 公有方法
      foo (baz) {
        this._bar(baz);
      }
    
      // 私有方法
      _bar(baz) {
        return this.snaf = baz;
      }
    
      // ...
    }
    
    // 第二种
    class Widget {
      foo (baz) {
        bar.call(this, baz);
      }
    
      // ...
    }
    
    function bar(baz) {
      return this.snaf = baz;
    }
    
    // 第三种 利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
    const bar = Symbol('bar');
    const snaf = Symbol('snaf');
    
    export default class myClass{
    
      // 公有方法
      foo(baz) {
        this[bar](baz);
      }
    
      // 私有方法 
      [bar](baz) {
        return this[snaf] = baz;
      }
    
      // ...
    };
    
    
    // 提案,不做正式用
    // 在属性名之前,使用#表示
    
    class Counter {
      #xValue = 0;
    
      constructor() {
        super();
        // ...
      }
    
      get #x() { return #xValue; }
      set #x(value) {
        this.#xValue = value;
      }
    }
    复制代码
    • new.target

      function Person(name) {
        if (new.target !== undefined) {
          this.name = name;
        } else {
          throw new Error('必须使用 new 命令生成实例');
        }
      }
      
      // 另一种写法
      function Person(name) {
        if (new.target === Person) {
          this.name = name;
        } else {
          throw new Error('必须使用 new 命令生成实例');
        }
      }
      
      var person = new Person('张三'); // 正确
      var notAPerson = Person.call(person, '张三');  // 报错
      
      // class 注意的是,子类继承父类时,new.target会返回子类
      class Rectangle {
        constructor(length, width) {
          console.log(new.target === Rectangle);
          this.length = length;
          this.width = width;
        }
      }
      
      var obj = new Rectangle(3, 4); // 输出 true
      
      
      // new.target会返回子类。利用这个特点,可以写出不能独立使用、必须继承后才能使用的类
      class Shape {
        constructor() {
          if (new.target === Shape) {
            throw new Error('本类不能实例化');
          }
        }
      }
      
      class Rectangle extends Shape {
        constructor(length, width) {
          super();
          // ...
        }
      }
      
      var x = new Shape();  // 报错
      var y = new Rectangle(3, 4);  // 正确
      
      复制代码
      • Object.getPrototypeOf 可以从子类上获取父类,可用作判断一个类是否继承自另一个类

        Object.getPrototypeOf(ColorPoint) === Point  // true
        复制代码
      • super 关键字

        • 有时当做函数,有时当做对象
        • 当函数使用时:子类中的构造函数顶部必须执行super();super() 相当于 父类.prototype.constructor.call(this);
        • 当对象使用时:super.xx 值父类的原型对象上的属性;相当于父类.prototype.xx
        • new.target.name 指向的永远是实例本身的构造函数

NAN

  • Number.NaN

  • Number.isNaN 相比于isNaN 不存在转换类型的行为

  • isNAN

    // 通过Number()强类型转换 将val 转成number类型,再判断是否为NaN, 所以 isNaN是判断val是否能转为数字,成功返回false,失败返回true
    console.log(isNaN(null));            //false
    console.log(isNaN(true));            //false
    console.log(isNaN(false));           //false
    console.log(isNaN(0));               //false
    console.log(isNaN(undefined));       //true
    console.log(isNaN("AB"));            //true
    console.log(isNaN({a: 1}));          //true
    console.log(isNaN(NaN));             //true
    
    // Number.isNaN()
    // Es6 新加的严格判断是否 === NaN
    console.log(Number.isNaN(null));      //false
    console.log(Number.isNaN(true));      //false
    console.log(Number.isNaN(false));     //false
    console.log(Number.isNaN(0));         //false
    console.log(Number.isNaN(undefined)); //false
    console.log(Number.isNaN("AB"));      //false
    console.log(Number.isNaN({a: 1}));    //false
    console.log(Number.isNaN(NaN));       //true
    
    
    // 区别
    isNaN("123Hello") // true
    isNaN("123") // false
    Number.isNaN("123Hello") // false
    Number.isNaN("123") // false
    复制代码

== 和 ===

==:相等运算符

  • 比较相同类型原始值与===一样

  • 比较不同类型的数据是

    • 如果类型为原始类型,则会将数据转换为数字类型再进行比较

    • 对象与原始类型值比较,对象转化为原始类型再进行比较

      '' == '0' // false
      0 == '' // true
      0 == '0' //true
      false == 'false' //false
      false == '0' // true
      false == undefined // false
      false == null // false
      null == undefined // true
      ' \t\r\n ' == 0 // true
      复制代码

===:严格运算符

* 不同类型值,如果俩个值得类型不同,直接返回false
* 同一类型的原始类型的值(数值,字符串,布尔值)比较时,值相同就返回true,值不同返回false
* 俩个复合类型(对象,数组,函数)的数据比较时,不是比较他们的值是否相同,而是比较他们是否指向同一个对象(内存地址)
* undefined 和 null 与自身严格相等
复制代码

类型转换

在 JavaScript 中有 6 种不同的数据类型:

  • string
  • number
  • boolean
  • object
  • function
  • symbol

3 种对象类型:

  • Object
  • Date
  • Array

2 个不包含任何值的数据类型:

  • null
  • undefined

全局方法String() 可以将数字转换为字符串,该方法可用于任何类型的数字,字母,变量,表达式

Number 方法toString() 也是同样的效果

// toExponential() 把对象的值转换为指数计数法
var num = 5.56789;
var n = num.toExponential(); // 5.56789e+0
// toFixed() 把数字转换为字符串,结果的小数点后有指定位数的数字
// toPrecision() 把数字格式化为指定的长度

// Number.isInteger(): 用来判断给定的参数是否为整数。
// Number.isSafeInteger(): 判断传入的参数值是否是一个"安全整数"。
Number.isInteger(10);        // 返回 true
Number.isInteger(10.5);      // 返回 false
复制代码
// typeof 操作符来查看JavaScript 变量的数据类型
typeof "John"                 // 返回 string
typeof 3.14                   // 返回 number
typeof NaN                    // 返回 number
typeof false                  // 返回 boolean
typeof [1,2,3,4]              // 返回 object
typeof {name:'John', age:34}  // 返回 object
typeof new Date()             // 返回 object
typeof function () {}         // 返回 function
typeof myCar                  // 返回 undefined (如果 myCar 没有声明)
typeof null                   // 返回 object
复制代码
// constructor 属性返回JavaScript变量的构造函数
"John".constructor                 // 返回函数 String()  { [native code] }
(3.14).constructor                 // 返回函数 Number()  { [native code] }
false.constructor                  // 返回函数 Boolean() { [native code] }
[1,2,3,4].constructor              // 返回函数 Array()   { [native code] }
{name:'John', age:34}.constructor  // 返回函数 Object()  { [native code] }
new Date().constructor             // 返回函数 Date()    { [native code] }
function () {}.constructor         // 返回函数 Function(){ [native code] }


// 使用constructor 属性来查看对象是否为数组(包含字符串“Array”)
function isArray(data){
   return data.constructor.toString().indexOf("Array") > -1;
}

// 使用constructor 属性来查看对象是否为日期
function isDate(data){
   return data.constructor.toString().indexOf("Date") > -1;
}
复制代码

把鼠标移到按钮并点击时

hover-focus-active

同步和异步的区别

我们知道 js 是一个单线程的操作,为什么说它是单线程呢?不可以是多线程呢?js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变

异步运行机制如下:

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
  3. 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

“异步模式”编程的4种方法

  1. 回调函数
    // setTimeout、setInterval
    function fun1(){
        setTimeout(()=>{
            cb();
        })
        console.log('fun1');
    }
    function fun2(){
        console.log('fun2');
    }
    fun1(fun2);
    
    // node
    var fs=require('fs');
    function getMime(callback){
      fs.readFile('mime.json',function(err,data){
          callback(data);
      })
    }
    getMime(function(result){
      console.log(result.toString());
    })
复制代码
  1. 事件监听,任务的执行不取决于代码的顺序,而取决于某个事件是否发生
    // 监听函数有:on, bind, listen, addEventListener, observe 
    
复制代码
  1. 发布订阅模式,也叫观察者模式
我们假定,存在一个“信号中心”,某个任务执行完成
复制代码
  1. Promises对象

浏览器的工作原理

  • Process-per-site-instance
  • Process-per-site
  • Process-per-tab
  • Single-process

click 优化

常用算法

冒泡

function bubbleSort(arr) {
    for (let i = 0, len = arr.length; i < len - 1; i++) {

        for (let j = 0; j < len - 1 - i; j++) {
            console.log(i, j, arr.join());
            count++;
            if (arr[j] - arr[j + 1] > 0) {
                let x = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = x;
            } else {
                // break;
            }
        }


    }
    return arr;
}

复制代码

事件冒泡和事件传递

DOM 2 级事件 规定的事件流包含三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,然后是实际的目标接收事件,最后阶段是冒泡阶段

1258248-20171122165221602-1768780949.png

事件传播三个阶段 (Event.prototype)
  • 0 NONE: 默认值,不代表任何意思
  • 1 CAPTURING_PHASE: 捕获阶段
  • 2 AT_TARGET: 目标阶段(当前事件源)
  • 3 BUBBLING _PHASE: 冒泡阶段

事件时用户或浏览器自身执行的某种动作,而响应某个事件的函数叫做事件处理程序,HTML事件处理程序。DOM 0 级事件处理程序和IE事件处理程序均以“on”开头,DOM 2 级事件处理程序不需要加“on”

  • DOM 0 级事件绑定:
    • dom.onclick = function(){}
  • DOM 2 级事件绑定
    • 标准浏览器: dom.addEventlistener(‘click’, function(){}, false);
    • IE 6-8:dom. (‘onclick’, function(){}), 事件对象通过window.event 来获取
  • 事件捕获是指从外层传递到里面,知道当前元素的事件行为

    • addEventlistener(eventName, function, true)
  • 事件冒泡:当前元素的某个事件行为被处罚,他的所有祖先元素(一直到document)的相关事件行为也会被触发执行(由里向外)

    • addEventlistener(eventName, function, false) window.event? window.event.cancelBubble = true : e.stopPropagation();
  • DOM 3 事件中stopImmediatePropagation()方法来阻止事件捕获,另外此方法还可以阻止事件冒泡

  • mouseover 存在冒泡传播机制,mouseenter冒泡传播机智被浏览器阻止

  • 阻止事件的默认行为及阻止

    • A标签有哪些默认行为
      1. 超链接:点击A标签可以实现页面的跳转
      2. 锚点定位:通过HASH值定位到当前页面的指定ID盒子位置
      • 首先URL地址栏末尾追加一个HASH;
      • 如果地址栏包含hash值,浏览器在渲染页面后,会默认定位到hash值得位置
      • 阻止A标签的默认行为
        1. 在HTML中 <a href="" />;
        2. 在JS中 e=e||window.event; e.preventDefault?e.preventDefault:e.returnValue = false;
  • 当一个容器内的很多元素都要为同一事件绑定方法,那么我们只需要给外层容器的该事件绑定方法,当里层元素的事件被出发时,会通过冒泡传播机制传到最外层容器那里,触发外层容器绑定的方法执行,在方法执行时,我们只需要根据判断事件源的不同而做不同的事情。(利用事件委托可提高50%左右的性能)

  • onDOMContentLoaded //当前浏览器中的DOM结构加载完成,就会触发这个事件

  • readystatechange // IE6-8下使用

this问题
标准:当事件行为被触发,方法中的this指向当前元素本身
IE6-8:当事件行为被触发,方法中的this指向window

五种主要数据类型的克隆(Bumber, String, Object, Array, Boolean)

  1. 使用typeof 判断值得类型;
  2. 使用toString区分数组和对象
  3. 使用递归函数
  function clone(obj){
    if(typeof obj === 'object' && typeof obj !== 'null'){
      var cloneObj = Obejct.prototype.toString.call(obj).slice(8, -1) === 'Array'?[]:{};
      for(let k in obj){
        if(typeof obj[k] === 'object' && typeof obj[k] !== 'null'){
          cloneObj[k] = clone(obj[k]);
        }else{
          cloneObj[k] = obj[k]
        }
      }
    }else{
      return obj;
    }
    return cloneObj;
  }
复制代码

去重方法, 排序算法,统计最多字母数

伪数组,如何转化为标准数组

伪数组是一个Object,存在的意义是可以让普通的对象也能正常使用数组的很多方法。比如:

let obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
}

;[].push.call(obj, 'd')
console.log([].slice.call(obj))

;[].forEach.call(obj, function(num, index){
  console.log(num)
})
复制代码

常见的伪数组有:

  • 函数内部的arguments
  • DOM对象列表(比如通过document.getElemntsByTags 得到的列表)
  • jQuery 对象(比如 $(“div”))

不同

  • 对象数组没有Array.prototype 属性, 类型是Obejct,而数组类型是Array
  • 数组是基于索引的实现,length会自动更新,而对象时键值对
  • 使用对象可以创建伪数组,伪数组可以使用数组的大部分方法

call 和 apply 区别

javascript 中 的每一个Function对象都有一个apply()方法和 call() 方法

定义

*  apply:调用一个对象的一个方法,用另一个对象替换当前对象。*例如*:B.apply(A, arguments); 即A对象应用B对象的方法
*  call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法。
复制代码

不同

* apply:最多只能有两个参数--新this对象和一个数组argArray,如果给改方法传递多个参数,则把参数都写入这个数组里面,当然,即使只有一个参数,也要写进数组里,如果argArray不是一个有效的数组和arguments对象,那么将导致一个TypeError。如果没有提供argArray 和 thisObj 任何一个参数,那么Global对象将被用作thisObj,并且无法被传递任何参数。
复制代码
  • call:它可以接受多个参数,第一个参数

与apply一样,后面则是一串参数列表。这个方法主要用在js对象各方法相互调用的时候,使当前this实例指针保持一致,或者在特殊情况下改变this指针。没有提供thisObj参数,那么Global
对象被用作thisObj。

  • apply和call的功能是一样的,只是传入

的参数列表形式不同

function add(a,b){
  return a+b;  
}
function sub(a,b){
  return a-b;  
}
var a1 = add.apply(sub,[4,2]);  //sub调用add的方法
var a2 = sub.apply(add,[4,2]);
alert(a1);  //6     
alert(a2);  //2

/*call的用法*/
var a1 = add.call(sub,4,2);
复制代码

callee和caller的作用


function factorial(x) {
	return x<=1 ? 1 : x*factorial(x-1);
}

// 使用 callee , callee 是arguments对象的一个属性,指向arguments对象的函数
function factorial(x) {
	return x<=1 ? 1 : x*arguments.callee(x-1);
}

// caller 函数对象的一个属性,指向调用当前函数的函数,比如A()调用B(),则在B()中B.caller指向A();
function B(){
	console.log(B.caller); // A();
}

(function A(){
	B()
}

// 如果c的执行环境为window则返回null
var c = function(x,y) {
   console.log(arguments.length,arguments.callee.length,arguments.callee)
   // arguments.callee.length 形参的长度
} ;

 c(1,2,3) ; // 3, 2, function(x, y){}
复制代码

注:在箭头函数中,this作用域跟函数外是一致的,且没有arguments对象,所以callee和caller在箭头函数中是无效的

一次完整HTTP 事务是怎样的过程

  • 域名解析
  • 发起TCP的3次握手
  • 建立TCP连接后发起的http请求
  • 服务端响应http请求,浏览器得到html代码
  • 浏览器解析html代码,并请求html代码中的资源
  • 浏览器对页面进行渲染呈现给用户

instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

ES5 对象方法

// 添加或更改对象属性
Object.defineProperty(object, property, descriptor)

// 添加或更改多个对象属性
Object.defineProperties(object, descriptors)

// 访问属性
Object.getOwnPropertyDescriptor(object, property)

// 以数组返回所有属性
Object.getOwnPropertyNames(object)

// 以数组返回所有可枚举的属性
Object.keys(object)

// 访问原型
Object.getPrototypeOf(object)

// 阻止向对象添加属性
Object.preventExtensions(object)

// 如果可将属性添加到对象,则返回 true
Object.isExtensible(object)

// 防止更改对象属性(而不是值)
Object.seal(object)

// 如果对象被密封,则返回 true
Object.isSealed(object)

// 防止对对象进行任何更改
Object.freeze(object)

// 如果对象被冻结,则返回 true
Object.isFrozen(object)
复制代码

IP

ip分5类,A,B,C,D,E类
它总共32位2进制位,每八位分为一组,组成一个十进制的数字。
A类ip前8个2进制位代表网络号,后面的代表主机
B类ip前16个2进制位代表网络号,后面的代表主机
C类ip前24个2进制位代表网络号,后面的代表主机
D,E类ip在中国没有,它是美国专用的。
所以,

A;1.0.0.0-127.255.255.255
B;128.0.0.0-191.255.255.255
C;192.0.0.0-223.255.255.255
D;224.0.0.0-239.255.255.255
E;240.0.0.0-254.255.255.255

上边A,B,C类的地址有私有ip,是不能在因特网中使用,只能在局域网中用。它们是
10.0.0.0-10.255.255.255
172.13.0.0-172.31.255.255
192.168.0.0-192.168.255.255

Html

这里是javascript中制作滚动代码的常用属性

页可见区域宽: document.body.clientWidth;
网页可见区域高: document.body.clientHeight;

window.innerHeight;
网页可见区域宽: document.body.offsetWidth (包括边线的宽);
网页可见区域高: document.body.offsetHeight (包括边线的宽);
网页正文全文宽: document.body.scrollWidth;
网页正文全文高: document.body.scrollHeight;
网页被卷去的高: document.body.scrollTop;
网页被卷去的左: document.body.scrollLeft;
网页正文部分上: window.screenTop;
网页正文部分左: window.screenLeft;
屏幕分辨率的高: window.screen.height;
屏幕分辨率的宽: window.screen.width;

屏幕可用工作区高度: window.screen.availHeight;

输入url到页面渲染,包含dns查找

  1. DNS 解析
  • 查找缓存
    • Chrome 搜索自身的 DNS 缓存, 看有没有该域名对应的 IP 地址
    • Chrome 会搜索操作系统自身的 DNS 缓存(浏览器没有找到缓存或者缓存已经失效)
    • 读取本地的 HOST 文件(操作系统中的 缓存没有找到)
  • 浏览器发起一个 DNS 的一个系统调用(向本地主控 DNS 服务器, 一般由宽带运营商提供的, 发起的一个域名解析请求)
    • 宽带运营商服务器查看本地缓存 有没有过期
    • 运营商服务器代替浏览器发起的一个迭代 DNS 解析的请求(
      baidu.com 的 IP 地址是多少?
      1、寻找根域的 DNS IP 地址
      2、COM 域 的 顶级域的 IP 地址
      3、baidu.com 域的 IP 地址)
  1. TCP 连接

  2. 发送 HTTP 请求

  3. 服务端处理请求并返回HTTP响应报文

  4. 浏览器解析渲染页面

行类元素

  • 常用
    a, button, input, label, select, textarea, script, span, img
    b(加粗,用于吸引读者的注意到该元素的内容上)
    i(斜体)
    br(在文本中生成一个换行(回车)符号)
    small(文本字体小一号,表示边注释和附属细则,包括版权和法律文本)
    abbr(缩写元素,可选title属性提供完整的描述)
    cite(表示一个作品的引用,且必须包含作品的标题,这个引用可能是一个根据适当的上下文约定关联引用的元数据的缩写)
    em(标记出需要拥护着重阅读的内容,是可以嵌套的,嵌套层级越深,则其包含的内容被认定为越需要着重的部分)


  • 不常用
    sub(定义了一个文本区域,出于排版的原因,与主要的文本相比,应该展示得更低并且更小。)
    sup(定义了一个文本区域,出于排版的原因,与主要的文本相比,应该展示得更高并且更小。)
    map( 属性 与 属性一起使用来定义一个图像映射(一个可点击的链接区域).)
    kbd(HTML键盘输入元素() 用于表示用户输入,它将产生一个行内元素,以浏览器的默认monospace字体显示。)
    strong(表示文本十分重要,一般用粗体显示。)
    code(呈现一段计算机代码)
    dfn(标记被定义的术语)
    samp(用于标识计算机程序输出,通常使用浏览器缺省的 monotype 字体), var(表示变量的名称,或者由用户提供的值。)
    bdo
    object(表示引入一个外部资源,这个资源可能是一张图片,一个嵌入的浏览上下文,亦或是一个插件所使用的资源。)
    q(引用标签 ()表示一个封闭的并且是短的行内引用的文本. 这个标签是用来引用短的文本,所以请不要引入换行符; 对于长的文本的引用请使用

    替代.)

标签表示其内容的着重强调
标签表示从正常散文中区分出的文本

块级元素

  • 常用
    div, form, header, footer, h1-h6, hr, ul, ol, p, section, canvas, aside, dd,dl,
    audio, video, table, thead,tbody,tfoot
  • 不常用
    noscript(如果页面上的脚本类型不受支持或者当前在浏览器中关闭了脚本,则在 HTML 元素中定义脚本未被执行时的替代内容。)
    pre(预格式化文本)
    address(联系方式信息)
    article(表示文档、页面、应用或网站中的独立结构,其意在成为可独立分配的或可复用的结构,如在发布中,它可能是论坛帖子、杂志或新闻文章、博客、用户提交的评论、交互式组件,或者其他独立的内容项目。)
    blockquote(代表其中的文字是引用内容。通常在渲染时,这部分的内容会有一定的缩进)
    output(标签表示计算或用户操作的结果)
    fieldset(用于对表单中的控制元素进行分组,包括 label)
    figure(代表一段独立的内容, 经常与说明(caption)
    配合使用, 并且作为一个独立的引用单元。当它属于主内容流(main flow)时,它的位置独立于主体。这个标签经常是在主文中引用的图片,插图,表格,代码段等等,当这部分转移到附录中或者其他页面时不会影响到主体。)
    figcaption(与其相关联的图片的说明/标题,用?于描述其父节点

    元素里的其他数据。)

    <figure>
      <img src="/media/cc0-images/elephant-660-480.jpg"
          alt="Elephant at sunset">
      <figcaption>An elephant at sunset</figcaption>
    </figure>
    复制代码

src 和 href 的区别

  • 请求资源类型不同
    href 是超文本引用的简写,用来为当前元素和文档之间建立连接,常用的是link, a 标签
    src 会将指向的资源下载并引用到当前文档中,常用的标签有script, img, iframe 标签
  • 作用的结果不同
    href 是为当前文档和引用资源建立联系,而src是替换当前的元素
    而 src 是替换当前的元素
  • 浏览器的解析方式不同
    href 引用的资源,浏览器会将其识别为 CSS 文档,并行下载资源并且不会停止对当前文档的处理,当浏览器解析到 src 时 ,会暂停其它资源的下载和处理,直接将该资源下载,编辑,执行完毕,图片和框架也是如此,类似于将所指资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因。

渐进增强(progressive enhancement)和优雅降级(graceful degradation)

  • 根据目标客户端来分情况

querySelector 和 getElement 选择器API 的区别

querySelectorAll()方法使用css选择器作为参数并返回一个NodeList——包含着匹配节点的类数组对象,该方法不会返回HTML集合,因此返回的节点不会对应实时文档结构,着也避免了HTML集合引起的性能问题。

DOM

使用只返回元素节点的API遍历DOM,因为这些API的执行效率比自己实现的效率更高

属性名 被替代属性
children childNodes
childElementCount childNodes.length
firstElementChild firstChild
lastElementChild lastChild
nextElementSibling nextSibling
previousElementSibling previousSibling
  • 对dom频繁操作,可以设置dom的display 属性为none,操作完成之后再修改为原display属性
  • 慎用:hover (如果有大量的:hover,那么会降低相应速度,CPU升高)

浏览器内核

Trident:IE,UC浏览器(Webkit内核+Trident内核)等。
Gecko:跨平台内核,火狐
Webkit:Safari
Blink:Chrome内核, Opera

瀑布流的实现

  // 设置图片宽度一致
  // 根据浏览器宽度以及每列宽度计算出列表个数,列表默认为0
  // 当图片加载完成,所有图片一次放置在最小的列数下面
  // 父容器高度去列表数组的最大值

  function fall() {
      const minGap = 20
      const itemWidth = 100
      const scrollBarWidth = getScrollbarWidth()
      const pageWidth = window.innerWidth - scrollBarWidth
      const column = Math.floor(pageWidth / (itemWidth + minGap))
      const gap = (pageWidth - itemWidth * column) / column / 2 // 真实的间距

      const items = document.querySelectorAll('.item')


      console.log('scrokkBarWidth', items);

      const heightArr = []

      function getScrollbarWidth() {
          const oDiv = document.createElement('div')

          oDiv.style.cssText = `width:50px; height:50px; overflow:scroll; background:red;`

          document.body.appendChild(oDiv);

          const scrollBarWidth = oDiv.offsetWidth - oDiv.clientWidth;

          oDiv.remove();

          return scrollBarWidth;
      }

      for (let i = 0, len = items.length; i < len; i++) {
          const height = items[i].offsetHeight;

          if (i < column) {
              items[i].style.cssText = `top:${gap}px; left:${(itemWidth + gap) * i +gap}px`

              heightArr.push(height);
          } else {
              let minHeight = heightArr[0];

              let minIndex = 0;
              for (let j = 0, jLen = heightArr.length; j < jLen; j++) {
                  if (minHeight > heightArr[j]) {
                      minHeight = heightArr[j]
                      minIndex = j;
                  }
              }

              items[i].style = `top:${minHeight+gap *2}px; left:${(itemWidth+gap) * minIndex + gap}px`

              heightArr[minIndex] = minHeight + gap + height;
          }
      }
  }
  // 页面加载完成调用一次。
  window.onload = fall;
  // 页面尺寸发生改变再次调用。
  window.onresize = fall;
复制代码

Css

批量改变样式

/* 使用cssText */
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 20px';
复制代码

css表达式使用一次性表达式(但最好避免css表达式)


// css
p{
    background-color: expression(altBgcolor(this))
}

// js
function altBgcolor(el){
    el.style.backgroundColor = (new Date()).getHours() % 2 ? "#fff" : "#06c";
}
复制代码

三角箭头

.triangle{
    width:0px;
    border-left:10px solid transparent;
    border-right:10px solid transparent;
    border-bottom:20px solid #000;
}
复制代码

盒子模型

CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。
盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。

  box-sizing: content-box|border-box|inherit:
复制代码
  • content-box: 这是 CSS2.1 指定的宽度和高度的行为。指定元素的宽度和高度(最小/最大属性)适用于box的宽度和高度。元素的填充和边框布局和绘制指定宽度和高度除外
  • border-box: 指定宽度和高度(最小/最大属性)确定元素边框。也就是说,对元素指定宽度和高度包括了 padding 和 border 。通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。
  • inherit: 指定 box-sizing 属性的值,应该从父元素继承

标准盒子模型和IE盒子的区别

一旦为页面设置了恰当的 DTD,大多数浏览器都会按照上面的图示来呈现内容。然而 IE 5 和 6 的呈现却是不正确的。根据 W3C 的规范,元素内容占据的空间是由 width 属性设置的,而内容周围的 padding 和 border 值是另外计算的。不幸的是,IE5.X 和 6 在怪异模式中使用自己的非标准模型。这些浏览器的 width 属性不是内容的宽度,而是内容、内边距和边框的宽度的总和。

虽然有方法解决这个问题。但是目前最好的解决方案是回避这个问题。也就是,不要给元素添加具有指定宽度的内边距,而是尝试将内边距或外边距添加到元素的父元素和子元素。

IE8 及更早IE版本不支持设置填充的宽度和边框的宽度属性。

居中的实现

宽高不固定
  • 方法一
    优点:content 可以动态改变高度(不需在 CSS 中定义)。当 wrapper 里没有足够空间时, content 不会被截断

    缺点:Internet Explorer(甚至 IE8 beta)中无效,许多嵌套标签(其实没那么糟糕,另一个专题)

      <div id="wrapper">
        <div id="cell">
          <div class="content">Content goes here</div>
        </div>
      </div>
    复制代码
      #wrapper {
        display: table;
      }
    
      #cell {
        display: table-cell;
        vertical-align: middle;
        text-align: center;
      }
    复制代码
    • 方法二

    缺点:IE(IE8 beta)中无效 无足够空间时,content 被截断,但是不会有滚动条出现

    #content {
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      margin: auto;
      height: 240px;
      width: 70%;
    }
    复制代码
    宽高固定
    • 方法一

    优点: 适用于所有浏览器 不需要嵌套标签
    缺点: 没有足够空间时,content 会消失(类似div 在 body 内,当用户缩小浏览器窗口,滚动条不出现的情况)

      <div id="wrapper">
        <div id="cell">
          <div class="content">Content goes here</div>
        </div>
      </div>
    复制代码
      #wrapper {
        width: 600px;
        height: 600px;
        position:relative;
      }
    
      #cell {
        width:200px;
        height:200px;
        position:absolute;
        left:50%;
        top:50%;
        margin-top: -100px;
        margin-left: -100px;
      }
    复制代码

动画

css Hack

针对不同的浏览器写不用的CSS,就是CSS Hack

条件Hack

属性级Hack

选择符Hack

px和em的区别

px和em都是长度单位,区别是,px的值是固定的,指定是多少就是多少,计算比较容易。em得值不是固定的,并且em会继承父级元素的字体大小。

浏览器的默认字体高都是16px。所以未经调整的浏览器都符合: 1em=16px。那么12px=0.75em, 10px=0.625em

优化

少设置全局变量,少全局查找(如需要全部变量,全局变量数据局部化)少闭包,及时清除定时器,事件委托,循环优先使用do…while…(合并循环变量和条件),for 循环减少长度获取,文档碎片代替append

代码执行 工具:JSBench

减少判断层级

减少作用域链查找层级

​~~~ js
// 全局变量局部化
fun1(){
const a = window.inneiHeight;
console.log(a);
}

// 文档碎片代替append
let frg = document.createDocumentFragment();
for(let i=0; i<1000; i++){
let el = document.createElement(‘p’)
el.innerHTML = “lg is a coder”
fra.appChild(el)
}
document.body.appChild(frg)


~~~ js
// 减少判断层级
// 方法1:
fun2(){
    if(){
       if(){
        
    	}
   }else{
       
   }
}
// 方法2:优先推荐
fun3(){
    if(){}
    if(){}
    if(){}
}
复制代码

内存

内存用于暂时存放CPU中的运算数据,与硬盘等外部存储器交换的数据。是外存与CPU进行沟通的桥梁

V8

  1. V8 是一款主流的JavaScript执行引擎

  2. 采用即时编译(转为机器码),其它引擎提前转为机器吗后在执行

  3. V8垃圾回收策略

    1. 分代回收
    2. 空间复制
    3. 标记清除
    4. 标记整理
    5. 标记增量

内存管理

  • 内存:由可读写单元组成,表示一片可操作空间,我们平常创建基本类型,对象,数组等都需要内存。既然有使用就会有管理。js中的内存管理分为三步:1. 申请内存空间, 2. 使用内存空间,3. 释放内存空间。

    在 js 中,不能像c和c++ 主动调用api 来管理

    let obj = {} 赋值 自动会分配一块内存

    obj.name=”lg” 使用空间

    obj = null; 释放内存

可达对象

  • 可以访问到的对象就是可达对象 (引用,作用域链)

  • 可达的标准就是从根出发是否能够被找到

  • js 中全局变量对象就是根 (全局上下文)

垃圾回收 简书

  • js 中内存管理是自动的

  • 对象不再被引用的时候是垃圾 (通过引用关系无法找到某些对象的时候)

  • 对象不能被正确访问的时候 (对象存在,因语法错误或结构错误无法找到的对象)

  • GC(垃圾回收) 算法

    • 找到内存中的垃圾,并释放和回收空间

      • 在运行中,上下文中不再需要时
      • 在代码中,外部环境找不到对象时
    • 常见GC算法

      • 引用计数

        • 给每个对象添加一个引用计数器,标识对象的引用次数,当一个新引用指向对象时计数器加1,当指向对象的引用失效时计数器减1,当计数器为0时会被立即回收

        • 优点:

          1. 立即回收

          2. 最大限度减少程序暂停

        • 缺点:

          1. 无法回收循环引用的对象
            function fn() {
              const obj1 = {}
              const obj2 = {}
              //全局作用域下找不到obj1,obj2,但是obj1.name,obj2.name还引用着,所以引用数不为0
              obj1.name = obj2;
              obj2.name = obj1
              return 'xxxx'
          }
          fn()
          复制代码

    2. 时间开销大(维护数值变化(监听),对象修改,引用计数也需修改)

* 标记清除

  * 分标记和清除两个阶段完成

  * 遍历所有对象找标记活动对象
    
    * 标记计数时会递归的查找引用层级关系,把可达对象进行标记
    
  * 遍历所有对象清除没有标记对象
    1. 清除没有标记的对象

    2. 标记清除将回收的空间放在空闲列表上面,方便后面的程序直接在这里申请内存使用。

  * 回收相应空间

  * 优点

    1. 解决对象循环引用不能回收的问题(相对于引用计数)

  * 缺点

    1. 空间碎片化(由于回收的空间地址不连续),不能是空间最大化使用
    2. 不会立即清除垃圾

* 标记整理

  * 标记整理算法是标记清除算法的一个升级。主要是为了减少碎片空间化。工作原理与标记清除算法相同。
  * 相比于标记清除,在清除没有标记的对象之前,把这些垃圾对象移动位置,也就是先进行整理到一块后再清理,这样减少了碎片化空间

* 分代回收

  * 内存设限:64位内存不超过1.5G,32位不超过800m
  
  * 内存空间分为 新生代(存活时间短)和老生代
  
    * 小空间用于存储新生代对象(32m[64位] | 16m[32位])
  
    * 新生代对象回收
  
      * 回收过程采用复制算法+标记整理
  
      * 新生代内存区分为二个等大小空间
  
      * 使用空间位From,空闲空间位To
  
      * 活动对象存储于From空间
  
      * 标记整理后将活动对象拷贝至To
  
      * From 与 To交换空间完成释放
  
        > ​	回收细节
        >
        > 1. 拷贝过程中可能出现晋升
        > 2. 晋升就是将新生代对象移动至老生代
        > 3. 一轮GC还存活的新生代需要晋升
        > 4. To空间的使用率超过25%
  
    * 老年代对象  (1.4G[64位] | 700m[32位]) 
  
      * 老年代对象就是存活时间长的对象
      * 主要采用标记清楚,标记整理,增量标记算法
      * 首先(主要)使用标记清除完成垃圾空间的回收
      * 采用标记整理进行空间优化
      * 采用增量标记来进行效率优化
复制代码

页面加载阶段

  • dns预解析
  • 使用cdn
  • 静态资源的压缩与合并
  • 减少http请求
  • 异步加载defer,async
  • 服务端渲染ssr
  • 多使用内存和缓存

页面渲染阶段

css放前面,js放后面

<html>
    <head></head>
	<body>
       <!-- 页面布局  -->
        <script></script>
    </body>	
</html>
复制代码
  • 减少dom查询,多次使用的保存为变量
  • 减少dom操作,统一通过dom片段操作
  • 事件函数的节流和防抖(手写)
  • 图片懒加载,预加载
  • 尽早进行操作,domContextLoad 与 load 
  • 使用字体图标或者svg来代替传统的png等格式的图片

代码层面

防抖,节流

// 防抖,在第一次触发事件时,不立即执行函数,而是给出一个期限值比如1000ms
// 如果短时间在大量触发同一事件,只会执行一次函数
// 就是事件触发后等待一会,看还触发不了,触发了重新等待执行
function debounce(fn, delay){
	let timer = null;
    return function(){
        if(time){
            clearTimeout(timer);
        }
        timer = setTimeout(fn, delay);
    }
}

// 节流
// 防止无聊人士没事瞎点击,在delay时间范围内多次触发只执行一次
let isClick = true;
function throttle(fn, delay){
    let valid = true;
    return function(){
        if(valid){
            valid = false;
            setTimeout(()=>{
                fn();
                valid = true;
            }, delay)
        }
    }
}
复制代码

动态脚本加载技术

function loadScript(url, cb){
    let script = document.createElement("script");
    script.type = "text/javascript";
    if(script.readyState){
        script.onreadystatechange = function(){
            if(script.readyState === 'loaded' || script.readyState === 'complete'){
                script.onreadystatechange = null;
                cb();
            }
        }
    }else{
        script.onload = function(){
            cb();
        }
    }
    script.src = url;
    document.getElementsByTagName('head')[0].appendChild(script);
}

// 使用
loadScript('./a.js',function(){
    loadScript('./b.js',function(){
        loadScript('./c.js',function(){
            console.log('加载完成')
        })
    })
})
复制代码

循环中减少属性查找并反转(可以提升50%-60%的性能)

// 不推荐
for(var i=0; i<tem.length; i++){
    process(item[i]);
}

// 推荐
// for 循环
for(var i=item.length; i--){
    process(item[i]);
}
// while循环
var j = item.length;
while(j--){
    process(item[i]);
}
复制代码

基于函数的迭代(比基于循环的迭代慢)

// for...in... 循环比较慢,因为每次迭代操作时会同时查询实例或原型属性
// for循环迭代比forEach函数迭代性能高
// 不推荐
items.forEach(function(value,index,array){
    process(value);
})
复制代码

长列表性能优化,只渲染可视区域的列表

长表格性能优化,通过canvers来绘制表格

操作dom,不要用原始的方式来操作,vue中,可用ref,el,事件中e.target

递归可做尾递归的优化

避免嵌套循环和死循环

尽可能的使用事件委托来处理事件的绑定


vue

合理使用keep-alive来缓存数据

路由懒加载

import配合箭头函数
require
组件懒加载,异步加载

服务端渲染ssr,优化seo,首页白屏

模板预编译,使用vue-template-loader,把模板编译成渲染函数


webpack

去掉无用代码 treeShaking

使用souceMap,来还原线上代码,方便定位线上问题

使用chunck

图片可以使用webp,优雅降级处理


项目

打包打多份,根据浏览器支持,拉取es6 或 es5语法

浏览器缓存的使用

开启gzip

使用chrome的性能分析工具,查找性能瓶颈

静态资源和服务不要放在同一台机器上,多个域名去并行加载解析


设计模式的出现是为了代码复用,增加可维护性

安全

死亡之Ping、Land 攻击、UDP 洪水、Smurf 攻击均是常见的DoS 攻击手段

DoS 攻击通常通过抑制所有或流向某一特定目的端的消息,从而使系统某一实体不能执行其正常功能,产生服务拒绝

DoS 攻击不需入目标系统,仅从外部就可实现攻击

存储

cookies,sessionStorage和localStorage

笔者是在去年秋招面试的头条教育线,顺利拿到了offer,把还记得的东西写下来,供大家参考一下。

一面:

笔试题

1.如果后端传给前端一个很大的数,前端会怎么样,该怎么处理?

  • 懒加载+分页

  • 虚拟滚动列表

  • js缓冲器来分片处理(思路,不懂)

    function multistep(steps,args,callback){
        var tasks = steps.concat();
    
        setTimeout(function(){
            var task = tasks.shift();
            task.apply(null, args || []);   //调用Apply参数必须是数组
    
            if(tasks.length > 0){
                setTimeout(arguments.callee, 25);
            }else{
                callback();
            }
        },25);
    }
    复制代码

2.new的过程

var Person = function(){};
var p = new Person();
复制代码

new 的过程拆分为以下三步

  • var p = {}; 也就是说,初始化一个对象p

  • p._proto_ = Person.prototype;

  • Person.call(p);也就是说构造p, 也称之为初始化p

    // 构造器函数
    let Parent = function (name, age) {
        this.name = name;
        this.age = age;
    };
    Parent.prototype.sayName = function () {
        console.log(this.name);
    };
    //自己定义的new方法
    let newMethod = function (Parent, ...rest) {
        // 1.以构造器的prototype属性为原型,创建新对象;
        let child = Object.create(Parent.prototype);
        // 2.将this和调用参数传给构造器执行
        let result = Parent.apply(child, rest);
        // 3.如果构造器没有手动返回对象,则返回第一步的对象
        return typeof result  === 'object' ? result : child;
    };
    //创建实例,将构造函数Parent与形参作为参数传入
    const child = newMethod(Parent, 'echo', 26);
    child.sayName() //'echo';
    
    //最后检验,与使用new的效果相同
    child instanceof Parent//true
    child.hasOwnProperty('name')//true
    child.hasOwnProperty('age')//true
    child.hasOwnProperty('sayName')//false
    复制代码

3.浏览器的缓存机制(强缓存和协商缓存)

浏览器在向浏览器请求资源时,首先判断是否命中强缓存,再判断是否是命中协商缓存

【强缓存】浏览器在加载资源时,会先根据本地缓存资源的header中的信息(Expires,Cache-Control)判断是否命中强缓存,如果命中则直接使用缓存中的资源不会再向服务器发送请求。【协商缓存】当强缓存没有命中的时候,浏览器会发送一个请求到服务器,服务根据header中的部分信息来判断是否命中缓存。如果命中,则返回304,告诉浏览器未更新,可使用本地的缓存。

​ 解释:就是本地没有过期使用本地缓存资源,如果本地资源过期了,询问服务器资源更新了没(是否命中协商缓存),没有更新(服务端返回304)继续使用本地缓存资源

Cache-Control字段

在请求中使用Cache-Control 时,它可选的值有:
img
在响应中使用Cache-Control 时,它可选的值有:
在这里插入图片描述

扩展知识:

DNS缓存

全称Domain Name System, 即域名系统。

万维网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用记住能够被机器直接读取的IP数串。DNS协议运行在UDP协议之上,使用端口号53.

DNS解析

又叫主机名解析。

通过域名得到该域名对应的IP地址的过程叫做域名解析

www.baidu.com – DNS解析 – 11.222.33.444

有DNS的地方就有缓存,浏览器,操作系统,Local DNS, 根域名服务器,他们都会对DNS结果做一定程度的缓存。

DNS查询过程如下:

  1. 首先搜索浏览器自身的DNS缓存,如果存在,则域名解析到此完成
  2. 如果浏览器中没有找到,那么会尝试读取操作系统的hosts文件看是否存在对应的映射关系,如果存在,则到此完成
  3. 如果本地hosts文件不存在对应关系,则查找本地DNS服务器(ISP服务器,或者自己手动设置DNS服务器),如果找到,则完成
  4. 如果本地DNS服务器还没有招待,它就会向根服务器发出请求,进行递归查询

CDN 缓存

全称 Content Delivery Network,即内容分发网络。

优势:

1. CND 节点解决了跨运营商和跨地域访问的问题,访问延时大大降低。
2. 大部分请求在CDN边缘节点完成,CDN起到了分流作用,减轻了源服务器的负载压力
复制代码

memory cache

MemoryCache顾名思义,就是将资源缓存到内存中,等待下次访问时不需要重新下载资源,而直接从内存中获取。Webkit早已支持memoryCache。
目前Webkit资源分成两类,一类是主资源,比如HTML页面,或者下载项,一类是派生资源,比如HTML页面中内嵌的图片或者脚本链接,分别对应代码中两个类:MainResourceLoader和SubresourceLoader。虽然Webkit支持memoryCache,但是也只是针对派生资源,它对应的类为CachedResource,用于保存原始数据(比如CSS,JS等),以及解码过的图片数据。

disk cache

DiskCache顾名思义,就是将资源缓存到磁盘中,等待下次访问时不需要重新下载资源,而直接从磁盘中获取,它的直接操作对象为CurlCacheManager。

  • |memory cache | disk cache
相同点 只能存储一些派生类资源文件 只能存储一些派生类资源文件
不同点 退出进程时数据会被清除 退出进程时数据不会被清除
存储资源 一般脚本、字体、图片会存在内存当中 一般非脚本会存在内存当中,如css等

4.用css3写一个环形进度条

5.一道关于promise的任务控制编程题

6.说说BFC是什么,能解决什么问题

面试流程

自我介绍然后说一个印象深刻的项目。。。

1.浏览器是怎么加载一个HTML的(解析dom,css,js过程。。。)

2.vue-router实现的原理

3.回流重绘,为什么用transform写动画不用position top left
4.if…else…多层嵌套怎么解决

5.编程题:n维数组转换成1维数组,比如:[1,[2,3],[[4],[5,6]]]变成[1,2,3,4,5,6]

二面:

前端负责人面,自我介绍然后聊项目经验(一定要讲一些出彩的项目,什么难点,如何突破),聊了些工作经历(为什么想换工作,以前的工作有什么收获,自身的缺点)。。。

技术题问的不多

1.写一个EventEmitter

三面:

主管面,介绍工作经历,聊项目亮点balabala

1.编程题,在一个无序数组中找到第二大的数

2.项目设计题,写一个飞机大战(写出类和属性方法,不需要实现)

3.飞机大战的各个动画是怎么动起来的(游戏引擎怎么运作)

4.当浏览器地址栏输入一个url到页面展现,这个过程?

5.https过程

6.http和tcp的关系

四面:

北京交叉面,主要是聊项目(有亮点的项目,涉及架构的项目等),工作中做了什么,未来职业规划是什么,面试官最后还给了一定的建议。

刷过的题目

下面是我花了将近一个月的时间整理的一份面试题库。这些面试题,包括我本人自己去面试遇到的,还有在面试之前刷过的题目,我都统一的整理了一下,希望对大家有用。

HTML

  • 浏览器页面有哪三层构成,分别是什么,作用是什么?
  • HTML5的优点与缺点?
  • Doctype作用? 严格模式与混杂模式如何区分?它们有何意义?
  • HTML5有哪些新特性、移除了哪些元素?
  • 你做的网页在哪些浏览器测试过,这些浏览器的内核分别是什么?
  • 每个HTML文件里开头都有个很重要的东西,Doctype,知道这是干什么的吗?
  • 说说你对HTML5认识?(是什么,为什么)
  • 对WEB标准以及W3C的理解与认识?
  • ……

img

CSS

  • 解释一下CSS的盒子模型?
  • 请你说说CSS选择器的类型有哪些,并举几个例子说明其用法?
  • 请你说说CSS有什么特殊性?(优先级、计算特殊值)
  • 常见浏览器兼容性问题与解决方案?
  • 列出display的值并说明他们的作用?
  • 如何居中div, 如何居中一个浮动元素?
  • 请列举几种清除浮动的方法(至少两种)?
  • block,inline和inlinke-block细节对比?
  • 什么叫优雅降级和渐进增强?
  • 说说浮动元素会引起的问题和你的解决办法
  • 你有哪些性能优化的方法?
  • ……

img

JavaScript

  • js的各种位置,比如clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop的区别?

  • js拖拽功能的实现

  • 异步加载js的方法

  • js的防抖与节流

  • 说一下闭包

  • 说说你对作用域链的理解

  • JavaScript原型,原型链 ? 有什么特点?

  • 请解释什么是事件委托/事件代理

  • Javascript如何实现继承?

  • 函数执行改变this

      <!-- 用箭头函数,指向本身函数对象 -->
      function fn(){
        console.log('real',this); // {a:100}
    
        var arr = [1,2,3];
        arr.map(item=>{
            console.log(this); // {a:100}
        })
      }
      fn.call({a:100}); // call是强制让fn函数对象指向{a:100}
    
        <!-- 不用箭头函数,this指向window -->
      function fn(){
      console.log('real',this); // {a:100}
    
      var arr = [1,2,3];
      arr.map(function(item){
          console.log(this); // window;
      })
       }
      fn.call({a:100});
    复制代码
  • babel编译原理

  • 函数柯里化

  • 说一下类的创建和继承

  • 说说前端中的事件流

  • 如何让事件先冒泡后捕获

  • 说一下图片的懒加载和预加载

  • js的new操作符做了哪些事情

  • 改变函数内部this指针的指向函数(bind,apply,call的区别)

  • Ajax解决浏览器缓存问题

  • ……

img

2.谈一谈 JavaScript 的异步?

答:setTimeout、MutationObserver、postMessage、Promise、async、await、generator

从 MutationObserver、postMessage 会牵扯到 vue 的 $nextTick

从 generator 会聊到 co.js 实现,代码不长,意思也好理解,但让我写还真没写出来,建议兄弟们好好看一遍!

从 Promise 和 setTimeout 会聊到下面要说的事件循环

3.浏览器和 nodejs 事件循环?

答:执行栈,promise 是 microTask,setTimeout 是 task

其中很多的阶段,可以从这里看到完整的模型介绍:html.spec.whatwg.org/multipage/w…

需要说出来的点:首先 setTimeout 并没有特殊,也是一个 task。另外每次的执行过 task 和 大量的 microtask(不一定在一次循环全执行完)后,会进行 renderUi 阶段,虽然不是每次事件循环都进行 renderUi ,但每次间隔,也就是传说中 60hz 的一帧 16ms

nodejs 事件循环略有不同…多了 process.nextTick 等

4.手写 Promise 或者 Promise 的静态方法

答:手写 Promise 尽量写出来 Promise 的状态,静态方法以及 .then.catch。当然更细节的还原可以看 Promise A+ 规范。

静态方法指 Promise.allSettled Promise.all Promise.race 等等。

5.手写节流与防抖

答:老生常谈,原理很简单,主要还是看会不会封装函数,以及封装的高级与否,考虑的情况是否全面。

6.手写 bind 函数

答:同上。

7.service worker 使用

答:缓存,渐进式应用,拦截处理

聊到 worker 可能还会聊到 web worker, shared worder 等等,如果有自信,或者工作对这方面有深入理解,可以秀一下。能体现出自己的优势…

8.严格模式

答:this 的 undefined,禁止 with,arguments 不允许更改,给只读对象赋值抛异常,变量需要先声明,call,apply 第一个参数不会被转换…

能答出来一些就行。

少量算法

  • 回文串,中心扩散法
  • 冒泡,快排
  • 二分查找
  • 二叉树
  • 动态规划 (没遇到考这个的,可能难度太高,面试官看我简单的半天才拿下,这个直接不考虑了,大佬可以关注一下)

作者:爱前端不爱恋爱
链接:zhuanlan.zhihu.com/p/362868129
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Vue

  • Vue中 key 值的作用
  • Vue 组件中 data 为什么必须是函数?
  • vuex的State特性是?
  • 介绍一下Vue的响应式系统
  • computed与watch的区别
  • 介绍一下Vue的生命周期
  • 为什么组件的data必须是一个函数
  • 组件之间是怎么通信的
  • Vue.cli中怎样使用自定义的组件?有遇到过哪些问题吗?
  • Vue如何实现按需加载配合webpack设置
  • 简单描述每个周期具体适合哪些场景
  • scss是什么?在Vue.cli中的安装使用步骤是?有哪几大特性?
  • 聊聊你对Vue.js的template编译的理解?
  • Vue 路由跳转的几种方式
  • Vue如何实现按需加载配合webpack设置?
  • Vue的路由实现:hash模式和history模式
  • Vue与Angular以及React的区别?
  • Vue路由的钩子函数
  • 什么是Vue的计算属性?
  • ……

img

React

  • 介绍一下react
  • React单项数据流
  • react生命周期函数和react组件的生命周期
  • react和Vue的原理,区别,亮点,作用
  • reactJs的组件交流
  • 有了解过react的虚拟DOM吗,虚拟DOM是怎么对比的呢
  • 项目里用到了react,为什么要选择react,react有哪些好处
  • 怎么获取真正的dom
  • 选择react的原因
  • react的生命周期函数
  • setState之后的流程
  • react高阶组件知道吗?
  • React的jsx,函数式编程
  • react的组件是通过什么去判断是否刷新的
  • 如何配置React-Router
  • 路由的动态加载模块
  • Redux中间件是什么东西,接受几个参数
  • redux请求中间件如何处理并发

img

浏览器

  • 跨标签页通讯
      // 第一种 BroadcastChannel  BroadCast Channel 可以帮我们创建一个用于广播的通信频道。当所有页面都监听同一频道的消息时,其中某一个页面通过它发送的消息就会被其他所有页面收到。它的API和用法都非常简单。
      //  在不同的页面中用相同的名字 new BroadcastChannel()
      const bc = new BroadcastChannel("afc");
      // 发送
      bc.postMessage("hello");
      // 监听
      bc.onmessage = function(e) {
          console.log("监听", e);
      }
      // 关闭
      bc.close();
    
    
      // 第二种 Server Worker Service Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。
      
    
    复制代码
  • 浏览器架构
  • 浏览器下事件循环(Event Loop)
  • 从输入 url 到展示的过程
  • 重绘与回流
  • 存储
  • Web Worker
  • V8垃圾回收机制
  • 内存泄露
  • reflow(回流)和repaint(重绘)优化
  • 如何减少重绘和回流?
  • 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
  • localStorage 与 sessionStorage 与cookie的区别总结
  • ……

img

服务端与网络

  • HTTPS和HTTP的区别
  • HTTP版本
  • 从输入URL到页面呈现发生了什么?
  • HTTP缓存
  • 缓存位置
  • 强缓存
  • 协商缓存
  • 缓存的资源在那里
  • 用户行为对浏览器缓存的影响
  • 缓存的优点
  • 不同刷新的请求执行过程
  • ……

img

算法与数据结构

  • 二叉树层序遍历
  • B树的特性,B树和B+树的区别
  • 尾递归
  • 如何写一个大数阶乘?递归的方法会出现什么问题?
  • 把多维数组变成一维数组的方法
  • 说一下冒泡快排的原理
  • Heap排序方法的原理?复杂度?
  • 几种常见的排序算法,手写
  • 数组的去重,尽可能写出多个方法
  • 如果有一个大的数组,都是整型,怎么找出最大的前10个数

img

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享