2019前端知识点复习

TypeScript

1.可兼容JavaScript;相比js,加入了注释;添加一个完整的类结构,更好的面向对象;

2.Mac OS X环境搭建:

2.1.安装homebrew(套件管理器)官网:brew.sh:

ruby -e "$(curl -fsSL 
https://raw.githubusercontent.com/Homebrew/install/master/install)" 

2.2安装npm(nodejs包管理器):

brew install node

2.3.安装typescript(安装成功后,可以键入tsc测试):

npm install -g typescript
tsc

3.IDE中新建typescript文件,添加watcher实现自动编译

也可以用命令的方式编译一个ts文件为js文件:终端目录下,tsc xx.ts,会生成同名js文件

4.TypeScript 7种 基础数据类型:

Boolean:

var isBone: boolean = false; //使用时要给个初始化的值

Number:

var height: number = 6;

String:

var name: string = "bob";

Array:

var list: number[] = [1, 2, 3]; 
// 或者
var list: Array<string> = [ "joe", ”kevin” ];

获取数组中数据:

List[0];

Enum:

枚举类型:有一定范围的数据可以用枚举类型最好

enum Color {Red, Green, Blue};

var colorName:string = Color[1];

console...

枚举类型还可以进行(数组)下标赋值操作:

enum Color {Red = 10, Green = 12, Blue = 13};
var colorName: string = Color[12]; // Green
// 获取下标:
var c:Color = Color.Green; // 1

Any:

任意类型:可以多次赋值,后面的会覆盖前面的。

var notSure: any = 10;
notSure = "hello";

也可以指定类型,如下指定为数组

Var list: any[ ] =  [1, “hello”, false];
List[2] // false

Void:

对函数进行声明:

function tell( ):string{
  return  "hello";
}

不需要任何返回值,就可以把函数声明为void

function tell1( ):void{}

类:

创建一个类:

class Preson{
  // 属性
  name:string;
  age:number;
  // 构造方法
  constructor(name:string, age:number){
    this.name = name;
    this.age = age;
  }
  // 普通方法
  print(){
    return this.name + " : " + this.age;
  }
}

调用这个类:

var p = new Person(“mike”, 10);
p.print();

类的继承

无构造函数的类的继承:

class Person{
  name: string;
  age: number;
  tell(){
    return this.name + ":" + this.age;
  }
}

class Student extends Person{
  school: string;
  tell(){
    return this.name + ":" + this.age + ":" + this.school; 
  }
}
var s = new Student("捷克学院");
// 没有构造函数,无法使用参数,就用如下方式传入参数值
s.name = "jack";
s.age = 23;
s.school = "捷克学院";
// 执行
s.tell();

有构造函数的类的继承:

class Person{
  name: string;
  age: number;
  constructor(name:string, age:number){
    this.name = name;
    this.age = age;
  }
  tell(){
    return this.name + ":" + this.age;
  }
}

class Student extends Person{
  school: string;
  constructor(school:string){
    // 修改父类的参数,super要写在最前面
    super("ime", 23);
    this.school = school;
  }
  tell(){
    return this.name + ":" + this.age + ":" + this.school; 
  }
}
var s = new Student("捷克学院");
// s.name = "jack";
// s.age = 23;
// s.school = "捷克学院";
s.tell();

访问修饰符

公有:public(默认)
私有:private

class Person{
  public name: string; 
  /**
  * 默认就是public,如果我在这里写private,
  * 或者在下面constructor参数中这样写:private name:string,
  * 那么继承自它的 Student 就无法访问到 name 这个属性了
  **/
  age: number;
  constructor(name:string, age:number){
    this.name = name;
    this.age = age;
  }
  tell(){
    return this.name + ":" + this.age;
  }
}

class Student extends Person{
  school: string;
  constructor(school:string){
    this.school = school;
    super("ime", 23);
  }
  tell(){
    return this.name + ":" + this.age + ":" + this.school; 
  }
}
var s = new Student("捷克学院");
// s.name = "jack";
// s.age = 23;
// s.school = "捷克学院";
s.tell();

封装的实现

· 利用private实现的私有属性,可以在类中通过getter和setter来对外开发(私有属性或方法)接口
· 在调用方法时,getter和setter会默认执行

class Hello{
  private _age:number;
  show(){
    return this._age; 
  }
  get age():number{
    // 可对外开发私有属性
    return this._age;
  }
  set age(newage:number){
    // 可修改私有属性
    if(newage >200 || newage < 0){
      alert("请输入正确的年龄");
    }else{
      this._age = newage;
    }
  }
}

var h = new Hello();
h.age = 300;
alert(h.tell());

static(静态)和其使用技巧

类的属性或方法(非静态),可以通过实例化对象调用,如下:

class Person{
  name:string;
  tell(){
    alert("姓名:" + this.name);
  }
}

var p = new Person();
p.name = "hello";
p.tell();

但一旦属性或方法声明为Static后,就变为了静态属性 / 静态方法
不能再通过实例化对象调用,必须通过类本身调用:

class Person{
  static name:string;
  tell(){
    alert("姓名:" + Person.name);
  }
}

var p = new Person();
Person.name = "hello";
p.tell();

static静态的使用(进阶)技巧:引用数据类型

class Greeter{
  greeting:string;
  constructor(msg:string){
    this.greeting = msg;
  }
  greet(){
    return "Hello," + this.greeting;
  }
}
/**
创建一个类型,指定它的类型为当前类的类型;
其实,创建了一个类,也就是创建了一个声明,
可以把当前数据的类型,声明为当前类的类型
我们称它为“引用数据类型”
**/
var green:Greeter;
green = new Greeter("mike");
alert(green.greet());

函数:函数类型

函数有 命名函数 和 匿名函数,在Ts中,可以指定函数参数的类型和函数本身(返回值)的类型:

// 命名函数
function add(x:number, y:number):string{
  // 如果返回值的类型不是函数指定的类型,就会报错
  // 例如:return x+y;就会报错
  return "hello ts";// 类型相符,不会报错
}
// 匿名函数(在js中,上面的命名函数是这种方式的语法糖)
var myadd = function(x:number, y:string):string{
  return "hello ts";
}

上面的x,y,并不清楚其意义,我们可以让参数更明确其意义:

/** 
* "=>" 前面可以声明参数的语义化名称和类型
* "=>" 后面紧跟着函数类型
**/
var myAddts:(name:string, age:number) => number = function(n, a){
  return a;
}

函数:可选和默认参数

可选参数:用 ?表示,可传可不传

function buildName(firstName:string, lastName?:string){
  if(lastName){
    return fristName + " " + lastName;
  } else {
    return firstName;
  }
}
// 均不会报错
var result1 = buildName("james", "bonde");
var result2 = buildName("bonde");
// 超出最大的参数个数,则会报错
// var result3 = buildName("james", "bonde", "valin"); 

默认参数:直接在参数上赋值

function buildName(firstName:string, lastName="bonde"){
  return firstName + " and " + lastName;
}
// 可以只传一个参数,也可以传两个参数,传两个,则会覆盖默认参数值
var result1 = buildName("Hello: ");
var result2 = buildName("james", "wade");
// 超出最大的参数个数,仍旧会报错
// var result3 = buildName("james", "bonde", "valin"); 

函数:可变参数

利用es6的扩展运算符(展开运算符)

function peopleName(firstName:string, ...restOfName:string[]){
  // restOfName:string[] 代表数据类型为字符串的数组
  return firstName + " " + restOfName.join(" ");
}
// 利用可变参数,可以传递无数个参数,在需要传入不确定参数个数时,很好用
var pn = peolpleName("jack", "mary", "honly", "kevin"...);

Lambads 和 this 关键字的使用

var people = {
  name:["iwen","ime","if","bean"];
  getName:function(){
    return function(){
      var i = Math.floor(Math.random()*4);
      return {
        n:this.name[i]
      }
    }
  }
}

var myName = people.getName();
alert("名字:" + myName().n); // 这是访问不到的

此时这个this指向的不是people。调用发现getName中的this关键字指向的是getName,访问不到外部的name属性。

可以用lambads表达式"()=>"修改,箭头函数能保存函数创建时的 this值,而不是调用时的值。

var people = {
  name:["iwen","ime","if","bean"];
  getName:function(){
    return () => {
      var i = Math.floor(Math.random()*4);
      return {
        // 这里的this代表函数创建时的this,因此可以访问到name属性
        n:this.name[i]
      }
    }
  }
}

var myName = people.getName();
alert("名字:" + myName().n); // 此时可访问

TypeScript 的重载

function attr(name:string):string;
fucntion attr(age:number):number;

function attr(nameOrAge:any):any{
  if(nameOrAge && typeof nameOrAge === "string"){  
    alert("姓名");
  }else{
    alert("年龄");
  }
 }

/**
*  在某些编程语言中,函数重载或方法重载是指能够创建具有不同实现的同名方法。
*  对重载函数的调用将运行与调用上下文相适应的该函数的特定实现,
*  允许一个函数调用根据上下文执行不同的任务。
*  这里如果有不清楚可以看一下这里:https://www.zhihu.com/question/63751258
**/
attr("Hello");
attr(19);

接口-创建接口

// 规范了参数的类型
function printeLabel(labelObj:{label:string}){
  console.log(labelObj.label);
}
var myObj = {label:"Hello"};
// var myObj = {label:10};
printeLabel(myObj);

接口-可选属性

interface USB{
  name:string;
  age:number;
}
function printUSB(pu:USB){
  console.log(pu.name);
}
// 如果不定义参数可选,my必须包含两个参数,即string类型的name和number类型的age,否则编译会报错
var my = {name: "ime", age: 100};
printUSB(my);

可选参数通过 " ?"定义

interface USB{
  name?:string;
  age?:number;
}
function printUSB(pu:USB){
  console.log(pu.name);
}
// 定义了参数可选,my就可以按照可选参数传递参数了。
var my = {name: "ime"};
printUSB(my);

接口-函数类型

用接口的类型 来 规定函数的类型

interface SearchFunc{
  (source:string, subString:string):boolean;
}
// 定义函数的类型为接口的类型
var mySearch:SearchFunc:
mySearch = function(src:string, sub:string){
  var result = src.search(sub);
  if(result != -1){
    return true;
  }else{
    return false;
   }
}

接口-数组类型

使用接口对数组进行规范化

interface StringArray{
  /**
  * 数组中index对应的是一个number类型,但数组最终返回的是一个string类型
  * 可理解为下标是number类型;数组中的数据是字符串类型
**/
  [index:number]:string; 
}

var myArray:StringArray;
// 规定了数组中必须是string类型,其他类型都不可以,即使为空,也必须是空字符串。
myArray = ["10", "12"];
alert(myArray[1]);

接口-class类型

interface ClockInterface{
  currentTime:Date;
  setTime(d:Date);
}
class Clock implements ClockInterface{
  currentTime:Date;
  setTime(d:Date){
    this.currentTime = d;
  }
  constructor(h:number; m:number){

  }
}

接口 - 接口继承与混合类型

接口的继承:

interface Shape{
  color:string;
}
interface PenStroke{
  penWidth:number;
}
interface Square extends Shape,PenStroke{
  sideLength:number;
}

var s = <Square>{}; // 将一个接口初始化在一个变量中
s.color = "blue";
s.penWidth = 10;
s.sideLength = 10;

接口的混合类型:

interface Counter{
  interval:number;
  reset():void;
  (start:number):string;
}

var c:Counter;
c(10);
c.reset();
...

认识泛型

可以让参数类型在使用时才指定类型,而不需要在方法定义时就指定,这样使用更加灵活;它相比于any类型,在使用方法时有更好的明确性。

function Hello(num:number):number{
  return num; // 必须是number类型
}
function hello(str:any):any{
  return str; // 可以是任意类型
}
// 泛型一般用<T>表示,也可以用其他的大写字母
function hello<T>(arg:T):T{
  return arg; // 在调用之前可以是任意类型
}
// 调用时指定参数类型后,就必须是这种类型
var output = hello<string>("world");

泛型的使用

Vue

1、谈谈你对MVVM开发模式的理解
Model:代表数据模型,数据和业务逻辑都在Model层中定义;
View:代表UI视图,负责数据的展示;
ViewModel:负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;

Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。

这种模式实现了Model和View的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作dom。

2、Vue 有哪些指令?
v-html、v-text、v-show、v-if、v-for等

3、v-if 和 v-show 的区别
v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。

4、简述Vue的响应式原理
当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
用ES5实现:

// 第一步 实现基本架构
// 第二步 把模型的数据 显示到视图
// 第三步  更新视图同步到模型,再更新视图
// 发布者
class Vue{
  constructor(options){
    this.options = options;
    this.$data = options.data;
    this.$el = document.querySelector(options,el);
    this._directives = {
       // 存放订阅者集合的数组对象  
    }
    this.Observer(this.$data);
    this.Compile(this.$el);
  }
  // 劫持数据
  Observer(data){
    // 更新视图  局部更新
    for(let key in data){
      this._driectives[key] = []; // 空类数组对象
      
      let val = data[key]; // 当前的值 
      let _this = this;
      Object.defineProperty(this.$data, key, {
        get: function(){
          return val;
        },
        set: function(newVal){
           if(newVal !== val){
              val = newVal;
              //_this._directives[key] 得到myText的数组
              _this._directives[key].forEach(watch=>{
                // 遍历订阅者的实例
                watch.update(); // 更新视图
              })
            }
        }
      });
    }
  }
  // 解析指令
  Compile(el){
    let nodes = el.children;] // 获取APP下面的子元素
    for(let i = 0;i < nodes.length; i++){
      let node = nodes[i]; // 当前元素
      if(node.children.length){
        // 如果当前元素含有子元素,就递归调用自己
        this.Compile(node);
      }
      if(node.hasAttribute('v-text')){
         // 获取属性值,对号加入对应的订阅者数组
         let attrVal = node.getAttribute("v-text")
         this._directives[attrVal].push(new Watcher(node, attrVal, this, 'innerHTML'));
      }
      if(node.hasAttribute('v-model')){
        let attrVal = node.getAttribute("v-model");
        this._directives[attrVal].push(new Watcher(node, attrVal, this, 'value'));
        // 监听文本框事件
        node.addEventListener('input', (function(){
          return function(){
            // console.log(node.value);
            // 更新视图到模型
            this.$data[attrVal] = node.value;
          }
        })());
      }
    }
  }
  class Watcher{
    constructor(el, vm, mySelf, attr){
      this.el = el; 
      this.el = vm; 
      this.el = mySelf; 
      this.el = attr; 

      this.update(); // 初始化数据
    }
    update(){
      // div对象[innerHTML] = vue对象.data['myText']
      // input对象[value] = vue对象.data['myText']
      this.el[this.attr] = this.mySelf.$data[this.vm];
    }
  }
}

在Vue3.0版本中式采用ES6的Proxy对象来实现。

// 获取段落节点
const paragraph = document.getElementById('pararaph');
// 获取输入框节点
const input = document.getElementById('input');

// 需要代理的数据对象
const data = {
  text:  'hello world'
}

const handler = {
  // 监控data中的text属性变化
  set: function(target, prop, value){
    if(prop === 'text'){
      // 更新值
      target[prop] = value;
      // 更新试图
      paragraph.innerHTML = value;
      input.value = value;
      retur true;
    } else {
      return false;
    }
  }
}
// 构造 proxy 对象
const myText = new Proxy(data, handler);

// 添加input 监听事件
input.addEventListener('input', function(e){
   myText.text = e.target.value; // 更新myText的值
});

初始化值
myText.text = data.text;

5、Vue中如何在组件内部实现一个双向数据绑定?
假设有一个输入框组件,用户输入时,同步父组件页面中的数据。
具体思路:父组件通过props传值给子组件,子组件通过 $emit 来通知父组件修改相应的props值,具体实现如下:

import Vue from 'vue'
const component = {
    props: ['value'],
    template: ` < div > <input type = "text"@input = "handleInput": value = "value" > </div>`,
    data(){
        return{}
    },
    methods:{
        handleInput(e){
             this.$emit('input',e.target.value)
        }
    }
}

new Vue({
    components:{CompOne:component},
    el:'#root',template:`<div><comp-one:value1="value"@input="value = arguments[0]"></comp - one > </div>`,
    data(){
        return{value:'123'}
    }
})

6、Vue中v-model的实现原理是怎样的?
当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新

7、Vue中如何监控某个属性值的变化?
比如现在需要监控data中, obj.a 的变化。Vue中监控对象属性的变化你可以这样:

watch: {
    obj: {
        handler(newValue, oldValue) {
            console.log('obj changed')
        },
        deep: true
    }
}

deep属性表示深层遍历,但是这么写会监控obj的所有属性变化,并不是我们想要的效果,所以做点修改:

watch: {
    'obj.a': {
        handler(newName, oldName) {
            console.log('obj.a changed')
        }
    }
}

还有一种方法,可以通过computed 来实现,只需要:

// 利用计算属性的特性来实现,当依赖改变时,便会重新计算一个新值。
computed: {
    a1() {
        return this.obj.a
    }
}

8、Vue中给data中的对象属性添加一个新的属性时会发生什么,如何解决?

<template>
    <div>
        <ul>
            <li v-for="value in obj" :key="value">
                {{value}}
            </li>
        </ul>
        <button @click="addObjB">
            添加obj.b
        </button>
    </div>
</template>
<script>
    export  default {
        data() {
            return {
                obj:
                {
                    a:  'obj.a'
                }
            }
        },
        methods: {
            addObjB() {
                this.obj.b = 'obj.b'console.log(this.obj)
            }
        }
    }
</script>
<style>
</style>

点击button会发现, obj.b 已经成功添加,但是视图并未刷新:


image.png

image.png

原因在于在Vue实例创建时, obj.b 并未声明,因此就没有被Vue转换为响应式的属性,自然就不会触发视图的更新,这时就需要使用Vue的全局api—— $set():

// $set() 方法相当于手动的去把 obj.b 处理成一个响应式的属性,此时视图也会跟着改变了:
addObjB() {
    this.$set(this.obj, 'b', 'obj.b')
    console.log(this.obj)
}

视图改变:


image.png

9、delete和Vue.delete删除数组的区别
delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。
Vue.delete / this.$delete 直接删除了数组 改变了数组的键值。

var a = [1, 2, 3, 4]
var b = [1, 2, 3, 4]
delete a[1]
console.log(a)
this.$delete(b, 1)
console.log(b)
image.png

image.png

10、如何优化SPA应用的首屏加载速度慢的问题?
· 将公用的JS库通过script标签外部引入,减小 app.bundel 的大小,让浏览器并行下载资源文件,提高下载速度;
· 在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundel 的体积,在调用某个组件时再加载对应的js文件;
· 加一个首屏loading图,提升用户体验;

11、前端如何优化网站性能?
1、减少 HTTP 请求数量
- css sprites
- 合并css和js文件,压缩
- 采用lazyLoad懒加载

2、控制资源文件加载优先级

3、利用浏览器缓存

4、减少重排(Reflow)
- 如果需要在DOM操作时添加样式,尽量使用增加class属性,而不是通过style操作样式。

5、减少 DOM 操作

6、图标使用 IconFont 替换

12、网页从输入网址到渲染完成经历了哪些过程?
- 输入网址;
- 发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;
- 与web服务器建立TCP连接;
- 浏览器向web服务器发送http请求;
- web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);
- 浏览器下载web服务器返回的数据及解析html源文件;
- 生成DOM树,解析css和js,渲染页面,直至显示完成;

Vue的生命周期

beforeCreate(创建前),在数据观测和初始化事件还未开始

created(创建后),完成数据观测,属性和方法的运算,初始化事件, $el 属性还没有显示出来

beforeMount(载入前),在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。

mounted(载入后),在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。

beforeUpdate(更新前),在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。

updated(更新后),在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

beforeDestroy(销毁前),在实例销毁之前调用。实例仍然完全可用。

destroyed(销毁后),在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

什么是vue生命周期?

Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。

2、vue生命周期的作用是什么?
它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

3、vue生命周期总共有几个阶段?
它可以总共分为8个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。

4、第一次页面加载会触发哪几个钩子?
beforeCreate、created、beforeMount、mounted 。

5、DOM 渲染在哪个周期中就已经完成?
mounted

Vue实现数据双向绑定的原理: Object.defineProperty()

1、首先Object.defineProperty()的用法:
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法:Object.defineProperty(obj, prop, descriptor)
obj:要在其上定义属性的对象。
prop:要定义或修改的属性的名称。
descriptror:将被定义或修改的属性描述符。

2、vue实现数据双向绑定主要是:
采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。

js通过Object.defineProperty()实现简单的双向绑定:

<body>
    <div id="app">
        <input type="text" id="txt">
        <p id="show">
        </p>
    </div>
</body>
<script type="text/javascript">
    var obj = {};

    Object.defineProperty(obj, 'txt', {
        get: function (){
            return obj
        },
        set: function (newValue) {
          document.getElementById('txt').value = newValue;
          document.getElementById('show').innerHTML = newValue
        }
    }) ;

    document.addEventListener('keyup', function (e) {
        obj.txt = e.target.value;
    });
</script>

js通过Observer、Compile、Watcher实现一个原生JS的双向绑定:

// 第一步 实现基本架构
// 第二步 把模型的数据 显示到视图
// 第三步  更新视图同步到模型,再更新视图
// 发布者
class Vue{
  constructor(options){
    this.options = options;
    this.$data = options.data;
    this.$el = document.querySelector(options,el);
    this._directives = {
       // 存放订阅者集合的数组对象  
    }
    this.Observer(this.$data);
    this.Compile(this.$el);
  }
  // 劫持数据
  Observer(data){
    // 更新视图  局部更新
    for(let key in data){
      this._driectives[key] = []; // 空类数组对象
      
      let val = data[key]; // 当前的值 
      let _this = this;
      Object.defineProperty(this.$data, key, {
        get: function(){
          return val;
        },
        set: function(newVal){
           if(newVal !== val){
              val = newVal;
              //_this._directives[key] 得到myText的数组
              _this._directives[key].forEach(watch=>{
                // 遍历订阅者的实例
                watch.update(); // 更新视图
              })
            }
        }
      });
    }
  }
  // 解析指令
  Compile(el){
    let nodes = el.children;] // 获取APP下面的子元素
    for(let i = 0;i < nodes.length; i++){
      let node = nodes[i]; // 当前元素
      if(node.children.length){
        // 如果当前元素含有子元素,就递归调用自己
        this.Compile(node);
      }
      if(node.hasAttribute('v-text')){
         // 获取属性值,对号加入对应的订阅者数组
         let attrVal = node.getAttribute("v-text")
         this._directives[attrVal].push(new Watcher(node, attrVal, this, 'innerHTML'));
      }
      if(node.hasAttribute('v-model')){
        let attrVal = node.getAttribute("v-model");
        this._directives[attrVal].push(new Watcher(node, attrVal, this, 'value'));
        // 监听文本框事件
        node.addEventListener('input', (function(){
          return function(){
            // console.log(node.value);
            // 更新视图到模型
            this.$data[attrVal] = node.value;
          }
        })());
      }
    }
  }
  class Watcher{
    constructor(el, vm, mySelf, attr){
      this.el = el; 
      this.vm= vm; 
      this.mySelf= mySelf; 
      this.attr= attr; 

      this.update(); // 初始化数据
    }
    update(){
      // div对象[innerHTML] = vue对象.data['myText']
      // input对象[value] = vue对象.data['myText']
      this.el[this.attr] = this.mySelf.$data[this.vm];
    }
  }
}

Vue组件间的参数传递

1、父组件与子组件传值
父组件传给子组件:子组件通过props方法接受数据;
子组件传给父组件: $emit 方法传递参数

2、非父子组件间的数据传递,兄弟组件传值
eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适(虽然也有不少人推荐直接用VUEX,具体来说看需求咯。技术只是手段,目的达到才是王道)。

Vue的路由实现:hash模式 和 history模式

hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用 window.location.hash 读取。特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。

history模式:history采用HTML5的新特性;且提供了两个新方法: pushState(), replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。

Vue与Angular以及React的区别?

1、与AngularJS的区别

相同点:都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。

不同点:AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。

2、与React的区别

相同点:React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。

不同点:React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。

vue路由的钩子函数

首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。

beforeEach主要有3个参数to,from,next。
to:route即将进入的目标路由对象。
from:route当前导航正要离开的路由。
next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。

vuex是什么?怎么使用?哪种功能场景使用它?

只用来读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。

在main.js引入store,注入。新建了一个目录store,… export 。

场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物
image.png

state

Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。

mutations

mutations定义的方法动态修改Vuex 的 store 中的状态或数据。

getters

类似vue的计算属性,主要用来过滤一些数据。

action

actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count ++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment');
    }
  }
 })

modules

项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}
const moduleC = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}
const store = new Vuex.Store({
  modules: {
    a:  moduleA,
    b:  moduleB,
    c:  moduleC
  }
})

其他知识点:

1、css只在当前组件起作用

答:在style标签中写入scoped即可 例如: <stylescoped></style>

2、v-if 和 v-show 区别

答:v-if按照条件是否渲染,v-show是display的block或none;

3、route和router的区别
route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。而router是“路由实例”对象包括了路由的跳转方法,钩子函数等。

http 和 https

HTTPS(Secure Hypertext Transfer Protocol)安全超文本传输协议:
它是一个安全通信通道,它基于HTTP开发,用于在客户计算机和服务器之间交换信息,它使用安全套接字层(SSL)进行信息交换,简单来说它是HTTP的安全版。它是由Netscape开发并内置于其浏览器中,用于对数据进行压缩和解压操作,并返回网络上传送回的结果。
HTTPS实际上应用了Netscape的安全套接字层(SSL)作为HTTP应用层的子层。(HTTPS使用端口443,而不是象HTTP那样使用端口80来和TCP/IP进行通信。)SSL使用40 位关键字作为RC4流加密算法,这对于商业信息的加密是合适的。
HTTPS和SSL支持使用X.509数字认证,如果需要的话用户可以确认发送者是谁。总的来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议要比http协议安全。

在URL前加https://前缀表明是用SSL(安全套接字)加密的,你的电脑与服务器之间收发的信息传输将更加安全。 Web服务器启用SSL需要获得一个服务器证书并将该证书与要使用SSL的服务器绑定。

HTTPS和HTTP的区别:

https协议需要到ca申请证书,一般免费证书很少,需要交费。
http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的。
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全。
详细参看:https://www.jianshu.com/p/6db0c6dc97a9

GET 和 POST

GET 和 POST 报文上的区别

先下结论,GET 和 POST 方法没有实质区别,只是报文格式不同。

GET 和 POST 只是 HTTP 协议中两种请求方式,而 HTTP 协议是基于 TCP/IP 的应用层协议,无论 GET 还是 POST,用的都是同一个传输层协议,所以在传输上,没有区别。

报文格式上,不带参数时,最大区别就是第一行方法名不同

POST方法请求报文第一行是这样的

POST /uri HTTP/1.1 \r\n

GET方法请求报文第一行是这样的

 GET /uri HTTP/1.1 \r\n

是的,不带参数时他们的区别就仅仅是报文的前几个字符不同而已

带参数时报文的区别呢? 在约定中,GET 方法的参数应该放在 url 中,POST 方法参数应该放在 body 中

举个例子,如果参数是 name=chengqm, age=22。
GET 方法简约版报文是这样的

GET /index.php?name=qiming.c&age=22 HTTP/1.1
Host:  localhost

POST 方法简约版报文是这样的

POST /index.php  HTTP/1.1
Host:  localhost
Content-Type:  application/x-www-form-urlencoded

name=qiming.c&age=22

现在我们知道了两种方法本质上是 TCP 连接,没有差别,也就是说,如果我不按规范来也是可以的。我们可以在 URL 上写参数,然后方法使用 POST;也可以在 Body 写参数,然后方法使用 GET。当然,这需要服务端支持。

常见问题:

  1. GET 方法参数写法是固定的吗?
    在约定中,我们的参数是写在 ? 后面,用 & 分割。
    我们知道,解析报文的过程是通过获取 TCP 数据,用正则等工具从数据中获取 Header 和 Body,从而提取参数。
    也就是说,我们可以自己约定参数的写法,只要服务端能够解释出来就行,一种比较流行的写法是 http://www.example.com/user/name/chengqm/age/22

  2. POST 方法比 GET 方法安全?
    按照网上大部分文章的解释,POST 比 GET 安全,因为数据在地址栏上不可见。
    然而,从传输的角度来说,他们都是不安全的,因为 HTTP 在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文。
    要想安全传输,就只有加密,也就是 HTTPS。

  3. GET 方法的长度限制是怎么回事?
    HTTP 协议没有 Body 和 URL 的长度限制,对 URL 限制的大多是浏览器和服务器的原因。
    浏览器原因就不说了,服务器是因为处理长 URL 要消耗比较多的资源,为了性能和安全(防止恶意构造长 URL 来攻击)考虑,会给 URL 长度加限制。

  4. POST 方法会产生两个TCP数据包?
    HTTP 协议中没有明确说明 POST 会产生两个 TCP 数据包,而且实际测试(Chrome)发现,header 和 body 不会分开发送。
    所以,header 和 body 分开发送是部分浏览器或框架的请求方法,不属于 post 必然行为。

  5. w3school 里面说 URL 的最大长度是 2048 个字符
    url 长度限制是某些浏览器和服务器的限制,和 HTTP 协议没有关系。

跨域 —— 9种跨域方式实现原理

参考:https://mp.weixin.qq.com/s/nkDkofBidKQFp5oG4-aJNg

什么是跨域?

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。

同源策略

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。

同源策略限制内容有:

Cookie、LocalStorage、IndexedDB 等存储性内容
DOM 节点
AJAX 请求发送后,结果被浏览器拦截了

但是有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<script src=XXX>

请求跨域了,那么请求到底发出去没有?

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

跨域解决方案:

(1)JSONP

(2)cors(后台设置:简单请求和复杂请求)

(3)postMeassage
HTML5 XMLHttpRequest Level 2 中的 API,windows属性,可用于解决:

  • 页面和其他打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递
  • 上面三个场景的跨域数据传递

(4)websocket
实现了浏览器与服务器的全双工通信,websocket和http都是应用层协议,基于TCP协议,但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。

(5)node中间件代理(两次跨域)

借助了服务器和服务器之间无需遵循同源策略的特点。
image.png

(6)nginx 反向代理
使用nginx反向代理实现跨域,是最简单的跨域方式。
支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。

实现思路:通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。

//porxy服务器
server {
  listen  80;
  server_name  www.domain1.com;
  location  /  {
    proxy_pas  http://www.domain2.com:8080; #反向代理
    proxy_cookie_domain  www.domain2.com  www.domain1.com;  #修改cookie里域名
    index  index.html  index.htm;
    #当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
    add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
    add_header Access-Control-Allow-Credentials true;
  }
}

最后通过命令行nginx -s reload启动 nginx

// index.html
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

// server.js
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
    var params = qs.parse(req.url.substring(2));
    // 向前台写cookie
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取
    });
    res.write(JSON.stringify(params));
    res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');

(7)window.name + iframe
window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

其中 a.html 和 b.html 是同域的,都是http://localhost:3000;而 c.html 是http://localhost:4000

// a.html(http://localhost:3000/b.html)
  <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
  <script>
    let first = true
    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    function load() {
      if(first){
      // 第1次onload(跨域页)成功后,切换到同域代理页面
        let iframe = document.getElementById('iframe');
        iframe.src = 'http://localhost:3000/b.html';
        first = false;
      }else{
      // 第2次onload(同域b.html页)成功后,读取同域window.name中数据
        console.log(iframe.contentWindow.name);
      }
    }
  </script>

b.html 为中间代理页,与 a.html 同域,内容为空。

// c.html(http://localhost:4000/c.html)
  <script>
    window.name = '我不爱你'
  </script>

通过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

(8)location.hash + iframe
实现原理: a.html 欲与 c.html 跨域相互通信,通过中间页 b.html 来实现。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。

具体实现步骤:一开始 a.html 给 c.html 传一个 hash 值,然后 c.html 收到 hash 值后,再把 hash 值传递给 b.html,最后 b.html 将结果放到 a.html 的 hash 值中。
同样的,a.html 和 b.html 是同域的,都是http://localhost:3000;而 c.html 是http://localhost:4000

// a.html
  <iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
  <script>
    window.onhashchange = function () { //检测hash的变化
      console.log(location.hash);
    }
  </script>
 // b.html
  <script>
    window.parent.parent.location.hash = location.hash
    //b.html将结果放到a.html的hash值中,b.html可通过parent.parent访问a.html页面
  </script>
 // c.html
 console.log(location.hash);
  let iframe = document.createElement('iframe');
  iframe.src = 'http://localhost:3000/b.html#idontloveyou';
  document.body.appendChild(iframe);

(9)document.domain + iframe
该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。
只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。

实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。

我们看个例子:页面a.zf1.cn:3000/a.html获取页面b.zf1.cn:3000/b.html中 a 的值

// a.html
<body>
 helloa
  <iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
  <script>
    document.domain = 'zf1.cn'
    function load() {
      console.log(frame.contentWindow.a);
    }
  </script>
</body>
// b.html
<body>
   hellob
   <script>
     document.domain = 'zf1.cn'
     var a = 100;
   </script>
</body>

总结:

  • CORS 支持所有类型的 HTTP 请求,是跨域 HTTP 请求的根本解决方案
  • JSONP 只支持 GET 请求,JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。
  • 不管是 Node 中间件代理还是 nginx 反向代理,主要是通过同源策略对服务器不加限制。
  • 日常工作中,用得比较多的跨域方案是 cors 和 nginx 反向代理

PWA

PWA全称Progressive Web App,即 渐进式WEB应用。

一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能

解决了哪些问题?
可以添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏
实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能
实现了消息推送
它解决了上述提到的问题,这些特性将使得 Web 应用渐进式接近原生 App。
更多可参看:https://segmentfault.com/a/1190000012353473?utm_source=tag-newest

原型与原型链

什么是原型?
在js中,所有对象都是Object的实例,并集成Object.prototype的属性和方法,但是有一些是隐性的。

所有引用类型都具有对象特性,可以自由扩展属性。

var obj = {};
obj.attribute = "new attr";
var arr = [];
arr.attribute = "new attr";
function fn () {}
fn.attribute = "new attr";

所有的引用类型(包括数组,对象,函数)都有隐性原型属性(proto), 值也是一个普通的对象。

console.log(obj.__proto__);

所有的函数,都有一个 prototype 属性,值也是一个普通的对象。

console.log(obj.prototype);

所有的引用类型的proto属性值都指向构造函数的 prototype 属性值。

console.log(obj.__proto__ === Object.prototype); // true

当试图获取对象属性时,如果对象本身没有这个属性,那就会去他的proto(prototype)中去寻找。

function Dog(name){ 
   this.name = name; 
} 
Dog.prototype.callName = function (){ 
   console.log(this.name,"wang wang"); 
}
let dog1 = new Dog("Three Mountain"); 
dog1.printName = function (){ 
   console.log(this.name);
}
dog1.callName();  // Three Mountain wang wang
dog1.printName(); // Three Mountain

原型链图:
image.png

作用域及闭包

只有函数才能创造作用域。
for if else 不能创造作用域。

this:

本质上来说,在 js 里 this 是一个指向函数执行环境的指针。this 永远指向最后调用它的对象,并且在执行时才能获取值,定义是无法确认他的值。

var a = { 
    name : "A", 
    fn : function (){
        console.log (this.name) 
    } 
} 
a.fn() // this === a 
a 调用了fn() 所以此时this为a
a.fn.call ({name : "B"}) // this === {name : "B"} 
使用call(),将this的值指定为{name:"B"}
var fn1 = a.fn 
fn1() // this === window
虽然指定fn1 = a.fn,但是调用是有window调用,所以this 为window

this 有多种使用场景:

  1. 作为构造函数执行:
function  Student(name,age) {
    this.name = name           // this === s
    this.age = age             // this === s
    //return  this
}
var s = new Student("py1988",30)
  1. 作为普通函数执行:
function  fn () {
    console.log (this)       // this === window
}
fn ();
  1. 作为对象属性执行:
var obj = {
    name : "A",
    printName : function () {
        console.log (this.name)  // this === obj
    }
}
obj.printName ()

4.call(), apply(), bind(): 这是重点!
三个函数都可以修改this的指向:

var name = "小明" , age = "17" 
var obj = { 
    name : "安妮", 
    objAge :this.age, 
    fun : function (like,dislike) { 
              console.log (this.name + "今年" + this.age ,"喜欢吃" + like + "不喜欢吃" + dislike) ;
           } 
    } 
var a = { name : "Jay", age : 23 } 
obj.fun.call(a,"苹果","香蕉") // Jay今年23 喜欢吃苹果不喜欢吃香蕉 
obj.fun.apply(a,["苹果","香蕉"]) // Jay今年23 喜欢吃苹果不喜欢吃香蕉 
obj.fun.bind(a,"苹果","香蕉")() // Jay今年23 喜欢吃苹果不喜欢吃香蕉

首先 call,apply,bind 第一个参数都是 this 指向的对象,call 和 apply 如果第一个参数指向 null 或 undefined 时,那么 this 会指向 windows 对象。

call,apply,bind 的执行方式如上例所示。call,apply 都是改变上下文中的 this,并且是立即执行的。bind 方法可以让对应的函数想什么时候调用就什么时候调用。

闭包:

闭包的概念很抽象,看下面的例子你就会理解什么叫闭包了:

function a(){
  var n = 0;
  this.fun = function () {
    n++; 
    console.log(n);
  };
}
var c = new a();
c.fun();  //1
c.fun();  //2

闭包就是能够读取其他函数内部变量的函数。在 js 中只有函数内部的子函数才能读取局部变量。所以可以简单的理解为:定义在内部函数的函数。

异步和单线程

我们先感受下异步。

console.log("start");
setTimeout(function () {
    console.log("medium");
}, 1000);
console.log("end");

使用异步后,打印的顺序为 start-> end->medium。因为没有阻塞。

为什么会产生异步呢?

首先因为 js 为单线程,也就是说 CPU 同一时间只能处理一个事务。得按顺序,一个一个处理。

如上例所示,第一步:执行第一行打印 “start”;第二步:执行 setTimeout,将其中的函数分存起来,等待时间结束后执行;第三步:执行最后一行,打印 “end”;第四部:处于空闲状态,查看暂存中,是否有可执行的函数;第五步:执行分存函数。

为什么 js 引擎是单线程?

js 的主要用途是与用户互动,以及操作 DOM,这决定它只能是单线程。例:一个线程要添加 DOM 节点,一个线程要删减 DOM 节点,容易造成分歧。

为了更好使用多 CPU,H5 提供了 web Worker 标准,允许 js 创建多线程,但是子线程受到主线程控制,而且不得操作 DOM。

任务列队

单线程就意味着,所有的任务都要排队,前一个结束,才会执行后面的任务。如果列队是因为计算量大,CPU 忙不过来,倒也算了。但是更多的时候,CPU 是闲置的,因为 IO 设备处理得很慢,例如 ajax 读取网络数据。js 设计者便想到,主线程完全可以不管 IO 设备,将其挂起,然后执行后面的任务。等后面的任务结束掉,在反过头来处理挂起的任务。

好,我们来梳理一下:

1)所有的同步任务都在主线程上执行,行程一个执行栈。

2)除了主线程之外,还存在一个任务列队,只要一步任务有了运行结果,就在任务列队中植入一个时间。

3)主线程完成所有任务,就会读取列队任务,并将其执行。

4)重复上面三步。

只要主线程空了,就会读取任务列队,这就是 js 的运行机制,也被称为 event loop(事件循环)。

ES6 语法

ES6,强制开启严格模式!

let 和 const

作用域的概念:
全局作用域,函数作用域,块作用域
<暂时性死区>

let和const不能重复定义;const声明时必须赋值,let 不必须;

解构赋值

解构赋值的分类:
数组解构赋值,对象解构赋值,字符串解构赋值,
布尔值解构赋值,函数参数解构赋值,数值解构赋值

使用场景:

数组结构赋值:

(1)默认值:

  function fn(a, b=2){
  ...
  }

(2)变量交换

{
  let a = 1;
  let b = 2;
  [a, b] = [b, a];
  console.log(a,  b);
}

(3)获取函数返回值

{
  function  f(){
    return [1,2,3,4];
  }
  let a,  b,  c,  d;
  [a,  b,c,d] = f();  //  接收所有值
  [a, , , b] = f();  // 接收指定值
  [a, ...b] = f(); //  分开接收
}

对象解构赋值:

{
  let o = {p:42,  q:true}
  let {p,q} = o;
  console.log(p,q);
}

(1)默认值:

{
  let {a = 5, b = 10} = {a : 3}
  console.log(a,b) // 3, 5
}

(2)json对象取值 :特别常用的场景!

{
  let data = {
    title: 'abc',
    test: [{
      title:  'test',
       desc:  '描述'
    }]
  }
  // 声明一个跟解构对象格式一致的对象,来接收对象中的值
  let {title : esTitle,  test: [{title: cnTitle}]} = data;
  console.log(esTitle,  cnTitle);
}

正则扩展:

构造函数的变化、正则方法扩展、u修饰符、y修饰符、s修饰符

es5中有一下两种正则表达式写法:

{
  // 两个参数的情况,前面是匹配字符串
  let regex = new RegExp('xyz', 'i');
  // 一个参数的情况,前面是正则表达式
  let regex2 = new RegExp(/xyz/i); 
}

es6中,允许前面是正则表达式,而后面跟着修饰符的两个参数的写法:

{
  let regex3 = new RegExp(/xyz/ig, 'i'); // 但这种写法后面的修饰符会覆盖正则中的修饰符
  //  flags是es6中正则表达式的新的API,输出的结果是正则表达式的修饰符
  console.log(regex3.flags);  //  输出的是 ' i '
}

y修饰符:

{
  let s = 'bbb_bb_b';
  let a1 = /b+/g;
  let a2 = /b+/y;
  console.log('one', a1.exec(s), a2.exec(s));
  console.log('two', a1.exec(s), a2.exec(s));
}

g和y修饰符的共同点:都是全局匹配
不同点:
g:可以不连续匹配,bbb_bb_b
y: 粘连模式,必须连续匹配b后面如果不是b了,就不会继续匹配
sticky可以查看是否开启了粘连模式:a1.sticky

u修饰符:

u修饰符,可以识别大于2个字节的字符;
es5中,. 能匹配任何字符,但是前提是小于两个字节的
s修饰符:es6中还未实现

字符串扩展:(要安装babel-polyfill)

处理unicode编码大于0xffff的字符 => 用{}包裹起来
`\u20BB7` => `\u{20BB7}`
es5中:
获取unicode编码对应的字符:fromCharCode()
获取字符对应的unicode编码:charCodeAt();

es6中:
获取unicode编码对应的字符:fromCharPoint();
获取字符对应的unicode编码:codePointAt(); // 能获取超过0xFFFF长度的编码,并正确显示

es6中有一个字符串遍历器接口,可以正确处理unicode编码大于0xFFFF的字符串

{
  let str = '/u{20BB7}abc';
  for(let code of str){
    console.log('es6', code);
  }
  //let  of 可以正确遍历出0xFFFF编码长度的字符串对应的字符,不会出现乱码
}

str.includes('x'):判断字符串中是否包含了某个字符
str.startsWith('x'):判断字符串是不是已某个字符开始的
str.endsWith('x'):判断字符串是不是已某个字符结束的
str.repeat(2):重复字符串

模板字符串(很重要):

`你好,${name},欢迎你!`

padStart 和 padEnd (这两个是ES7的草案,需要babel-polyfill)

console.log('1'.padStart(2,  '0')); //  '01'
console.log('1'.padEnd(2,  '0')); //  '10'

标签模板:

1.怎么用?

{
  let user = {
    name:   'list',
    info:  'hello world'
  };
  console.log(abc`i am ${user.name}, ${user.info}`);
   function abc(s,  v1,  v2){
    console.log(s,  v1,  v2);
    return s+v1+v2;
  }
}

2.在哪里用?
防止XSS攻击,和处理多语言转换的时候用

string.raw:可以将所有的 ' \ ' 进行了转义,即在前面又加了一个
String.raw`Hi\n${1+2}` // Hi\n3

数值扩展:

Number.isFinite() :判断一个值是否有尽
Number.isNaN() :判断一个数是不是NaN
Number.isInteger() :判断是不是整数 2.0也是true

Number.MAX_SAFE_INTEGER // 最大安全数值
Number.MIN_SAFE_INTEGER // 最小安全数值

Number.isSafeInteger() // 判断一个数字是不是在安全数的范围内:
Math.trunc(4.1); //4 // 取小数的整数部分
Math.sign() // 结果只有-1, 0 ,1, NaN // 判断是正数,负数,还是零
Math.cbrt(8); // 2 // 计算立方根

还有三角函数方法,对数方法,都是es6新增的

数组扩展:

Array.from
Array.of
copyWithin
find \ findIndex
fill
entries \ keys \ values
includes

函数扩展:

1、参数默认值

{
  function test(x, y = " 23 "){
    console.log(x,  y);
  }
  test(2) // 2  23
}
//  要注意默认值后面不能再有没有默认值的参数变量
//  这就是错误的
 function test(x, y = " 23 " , c)  ...

关于取值作用域的问题:

{
  let x = 'test';
  function test(x, y=x){
    console.log(x,y);
  }
  test('kill'); //  kill kill
}

2、rest参数
把一系列的参数都转换为数组,在你不确定有多少个参数的时候
注意,rest参数后面不能再有其他的参数

{
 function test( ...arg ){
   for (let v of arg){
     console.log(v);
   }
 }
 test(1,2,3,4); // 1 2 3 4
}

3、扩展运算符
扩展运算符和rest运算符是一个相反的作用
它的作用是:把一个数组拆成离散的值

{
  console.log(...[1,2,3]); // 1  2  3
  console.log('a', ...[1,2,3]) //  'a'  1  2  3
}

4、箭头函数

{
  let arrow = v => v*2;
  //  等同于
  function arrow(v){
    return v*2;
  }

  // 无参数
  let arrow = () => v*3;
  //  等同于
  function arrow(){
    return v*3;
  }
}

5、this绑定
因为this在指向在箭头函数和普通函数不一样,所以要考虑什么时候适合用箭头函数,什么时候不适合用箭头函数;
this在普通函数中,指向函数被调用时的对象所在;
this在箭头函数中,指向函数被定义时的对象所在;
6、尾调用
区分特征:函数的最后一句话是不是函数

{
  function tail(x){
    console.log('tail',  x);
  }
  function fx(x){
    return tail(x);
  }
  fx(123);
}
//  尾调用的用处:提升(递归函数,嵌套函数)性能

对象(object)扩展:

1、简介表示法:

{
  let o = 1;
  let k = 2;
  //  es5中声明一个对象
  let es5 = {
    o:  o,
    k:  k
  };
  //  es6中,如果键值相同,可以这样写:
  let es6 = {
    o,
    k
  };
   
  //  如果对象里面有方法:
   let es5_method = {
    hello: function(){}
  };
  let es6_method = {
    hello(){}
  }
}

2、属性表达式:

{
  let a = 'b';
  let es5_obj = {
    a: 'c',
    b: 'c'
  };
  // 对象的key值,可以是一个表达式,这叫做属性表达式
  //  这里的 [a] = 'b';
  let es6_obj = {
    [a]:  'c'
  }
  console.log(es5_obj,  es6_obj);
}

3、扩展运算符:
babel对它的支持不是很好,加了babel-polyfill还是会报错

{
  let {a, b, ...c} = {a: 'test', b:'re', c:'ccc', d:'ddd'};
  解析过后c的值应该是:
  c: {
    c:'ccc',
    d:'ddd'
  }
}

4、Object新增方法:

  //  新增API
  Object.is() 的功能和 ‘===’ 没有区别,判断两个值是否相等
  Object.is(str, str); //判断两个字符串是否相等
  //  要注意,数组和对象是引用类型,虽然都是空数组,但是引用的地址不同,所以:
  object.is([],[])  // 结果是false

  Object.assign() : 浅拷贝
  //  拷贝的是引用地址,不是值;
  //  只拷贝自身的属性,不会拷贝继承的属性,和不可枚举的属性
  Object.assign({a:''a}, {b:'b'}); // {a: 'a', b: 'b'}

  Object.entries() : 方法返回一个给定对象自身可枚举属性的键值对数组,
  其排列与使用 for...in 循环遍历该对象时返回的顺序一致
  区别在于 for-in 循环也枚举原型链中的属性

  const object1 = { foo: 'bar', baz: 42 };
  console.log(Object.entries(object1)[1]);
  // expected output: Array ["baz", 42]

  const object2 = { 0: 'a', 1: 'b', 2: 'c' };
  console.log(Object.entries(object2)[2]);
  // expected output: Array ["2", "c"]

  const result = Object.entries(object2).sort((a, b) => a - b);
  console.log(Object.entries(result)[1]);
  // expected output: Array ["1", Array ["1", "b"]]

Symbol:

1、Symbol的概念:
Symbol可提供一个独一无二的值

let a1 = Symbol();
let a2 = Symbol();
console.log(a1 === a2); // false

// 定义了以后,将来要取回来,就用这种方式
let a3 = Symbol.for('a3'); // for中的a3是一个key值,如果在全局注册过,就返回这个值,如果没有注册过,就注册一个独一无二的值

let a4 = Symbol.for('a3');
let a5 = Symbol.for('a3');
console.log(a4 === a5); // true

2、Symbol的作用:

{
  let a1 = Symbol.for('abc');
  let obj = {
    [a1] : '123',
    'abc' : 234,
    'c' : 456
  }
  console.log('obj', obj); // {Symbol(abc): '123', 'abc': 234, 'c': 456}

要注意一点:用Symbol做key值,通过for...inlet...of是拿不到key值的

  for(let [key, value] of Object.entries(obj)){
    console.log(key, value) ; //拿不到Symbol(abc)和其值
  }

这时可以通过Object.getOwnPropertySymbols()拿到Symbol定义的值,但是它拿不到其他的值

  Object.getOwnPropertySymbols(obj).forEach(function(item){
    console.log(obj[item]); // 123
  })

如果想拿到所有的值,可以通过Reflect.ownKeys()拿到

  Reflect.ownKeys(obj).forEach(function(item){
    console.log('ownKeys', item, obj[item])
  })

ES6数据结构:

Set:

1、类似数组,但集合中的元素不能重复

{
  let list = new Set();
  list.add(5); // 添加
  list.add(7);
 
  list.size // 获取set中的长度(个数)

  // 初始化set中的值
  let arr = [1,2,3,4,5];
  let list1 = new Set(arr);

  // 不可重复性
  let list2 = new Set();
  list2.add(1);
  list2.add(2);
  list2.add(1);

  console.log(list2); // 不会打印出后面重复的元素,也不会报错,这可以用于去重

  这里要注意,Set不会做数据类型的转换,即 2 和 '2' 在set看来是不一样的

2、Set实例的几个方法和属性值:
add():添加某个元素
delete():删除某个元素
clear():清空所有元素
has():判断有没有某个元素,返回布尔类型
size:返回集合中元素的个数

3、Set实例的遍历:

// for...of
for(let key of list.keys()){
  console.log('keys', key);
}
for(let value of list.values()){
  console.log('value', value);
}
for(let value of list){
  console.log('value', value);
}
for(let [key,  value] of list.entries()){
  console.log('entries', key, value);
}
key和value的值一样,都是元素的名称;
不加.key或者.values,也是可以的

Map

1 、类似Object,key可以是任意类型(object中key只能是字符串)

{
  let map = new Map();
  let arr = ['123'];
  
  map.set(arr, 456); // Map可以用数组作为key(或其他任意类型)哦~
  console.log('map', map, map.get(arr));
  // 结果:map  Map{["123"] => 456}  456

  // 初始化Map中的值
  let map1 = new Map([['a', 123], ['b', 456]]);  // 一定要注意这种格式
}

2、Map实例的几个方法和属性值:
get():获取某个元素
set():添加某个元素
delete():删除某个元素
clear():清空所有元素
has():判断有没有某个元素,返回布尔类型
size:返回集合中元素的个数

3、Map 结构转为数组结构
比较快速的方法是结合使用扩展运算符(...)

let map = new Map([
   [1, 'one'],
   [2, 'two'],
   [3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

4、Map 循环遍历
Map 原生提供三个遍历器:
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。

let map = new Map([
 ['F', 'no'],
 ['T', 'yes'],
]);

for (let key of map.keys()) {
 console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
 console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
 console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
 console.log(key, value);
}

// 等同于使用map.entries()
// 这个例子可以看出:Map 结构的默认遍历器接口(Symbol.iterator 属性),就是 entries 方法。
for (let [key, value] of map) {
 console.log(key, value);
}

5、Map 获取第一个元素

const m = new Map();
m.set('key1', {})
m.set('keyN', {})

console.log(m.entries().next().value); // [ 'key1', {} ]

// 获取第一个key
console.log(m.keys().next().value); // key1

// 获取第一个value
console.log(m.values().next().value); // {}

Map与Array的对比 、 Set与Array的对比

数据结构横向对比,增、查、改、删

// Map与Array
let map = new Map();
let array = [];

// 增
map.set('t',  1);
array.push({t:1});
console.info('may-array', map,  array);

// 查
let map_exist = map.has('t');
let array_exist = array.find(item=>item.t);
console.info('may-array',  map_exist, array_exist); //map返回的是布尔值,数组返回的是这个值

// 改
map.set('t',  2);
array.forEach(item => item.t ? item.t = 2 : ' '); // 先要判断是否存在,才执行
console.info('may-array-modify',  map, array); 

// 删
map.delete('t');
let index = array.findIndex(item => item.t); // 查找每一个元素中带t的元素
array.splice(index, 1);
console.info('may-array-del', map, array); 


// Set与Array
let map = new Set();
let array = [];

// 增
set.add({t :1});
array.push({t:1});
console.info('set-array', set,  array);

// 查
let set_exist = set.has({t:1}); //这里应该是填入保存的地址,这样写会报错 
let array_exist = array.find(item=>item.t);
console.info('may-array',  set_exist, array_exist); //set返回的是布尔值,数组返回的是这个值

// 改
set.forEach(item => item.t ? item.t = 2: ' ');
array.forEach(item => item.t ? item.t = 2 : ' '); // 先要判断是否存在,才执行
console.info('may-array-modify',  set, array); 

// 删
set.forEach(item => item.t ? set.delete(item) : ' ');
let index = array.findIndex(item => item.t); // 查找每一个元素中带t的元素
array.splice(index, 1);
console.info('may-array-del', set, array); 

Proxy和Reflect

Proxy:可以用来代理对对象的访问
Reflect 的方法和用法和Proxy一模一样。只是使用方式不一样,Proxy需要new,而Reflect直接使用(Reflect.get())

// Proxy的几个常用方法:
// 其中target为代理对象参数,原本obj的属性,它都有,实际传入的代理对象,是proxy的实例化对象

// 获取对象属性
get(target, key){}

// 设置对象属性
set(target, key, value){}

// 判断对象是否有某个属性,可以拦截判断,欺骗访问
has(target, key){}

// 删除对象属性:拦截delete,如果不想用户删除对象中的某些属性
deleteProperty(target, key){}

// 拦截Object.keys, Object.getOwnPropertySymbols, Object.getOwnPropertyNames
ownKeys(){}

完整示例,及Proxy在开发中的使用:

{
  let obj = {
    time: '2017-03-11',
    name: 'net',
    _r: 123
  }

  let monitor = new Proxy(obj, {
    // 拦截对象属性的读取
    get(target, key) {
      return target[key].replace('2017', '2018')
    },
    // 拦截对象设置属性
    set(target, key, value) {
      if (key === 'name') {
        return (target[key] = value)
      } else {
        return target[key]
      }
    },
    // 拦截key in object操作
    has(target, key) {
      if (key === 'name') {
        return target[key]
      } else {
        return false
      }
    },
    // 拦截delete
    deleteProperty(target, key) {
      if (key.indexOf('_') > -1) {
        delete target[key]
        return true
      } else {
        return target[key]
      }
    },
    // 拦截Object.keys,Object.getOwnPropertySymbols,Object.getOwnPropertyNames
    ownKeys(target) {
      return Object.keys(target).filter(item => item != 'time')
    }
  })

  console.log('get', monitor.time)

  monitor.time = '2018'
  monitor.name = 'mukewang'
  console.log('set', monitor.time, monitor)

  console.log('has', 'name' in monitor, 'time' in monitor)

  // delete monitor.time;
  // console.log('delete',monitor);
  //
  // delete monitor._r;
  // console.log('delete',monitor);
  console.log('ownKeys', Object.keys(monitor))
}

{
  let obj = {
    time: '2017-03-11',
    name: 'net',
    _r: 123
  }

  console.log('Reflect get', Reflect.get(obj, 'time'))
  Reflect.set(obj, 'name', 'mukewang')
  console.log(obj)
  console.log('has', Reflect.has(obj, 'name'))
}

// proxy常用的情景:对数据类型进行校验,实现与业务解耦
{
  function validator(target, validator) {
    return new Proxy(target, {
      _validator: validator,
      set(target, key, value, proxy) {
        // 判断是否有对应可操作的属性
        if (target.hasOwnProperty(key)) {
          let va = this._validator[key]
          if (!!va(value)) {
            // 如果所访问的属性有值,则执行代理操作(这里仅以Set为例),将代理返回到真实对象proxy
            return Reflect.set(target, key, value, proxy)
          } else {
            throw Error(`不能设置${key}到${value}`)
          }
        } else {
          // 如果没有,则抛出异常
          throw Error(`${key} 不存在`)
        }
      }
    })
  }

  // 模拟一个条件类
  const personValidators = {
    name(val) {
      return typeof val === 'string'
    },
    age(val) {
      return typeof val === 'number' && val > 18
    },
    mobile(val) {}
  }

  // 模拟一个被代理的类
  class Person {
    constructor(name, age) {
      this.name = name
      this.age = age
      this.mobile = '1111'
      // 这里的this,指的是Person的实例化对象
      // Person类返回一个validator方法,validator方法返回一个proxy代理,
      //也就是说访问Person的实例,会经过validator中的proxy代理,进行筛选过滤
      return validator(this, personValidators)
    }
  }

  // 实例化Person类
  const person = new Person('lilei', 30)
  console.info(person)
  person.name = 'Han mei mei'
  console.info(person)
}

ES6中的 类和对象


基本语法 | 类的继承 | 静态方法 | 静态属性 | getter | setter

1、基本定义和生成实例

 class Parent{
    // 定义类中的构造函数
    constructor(name = 'zk'){
      this.name = name;
    }
}

let v_parent = new Parent('z');
console.log('构造函数实例', v_parent)

2、类的继承
定义一个父类,子类来继承

class parent{
  constructor(name='zk'){
    this.name = name;
  }
}

class child extends parent{
    // 继承父类的子类,可以覆盖 / 把实际需要的参数传递给 父类的参数
    constructor(name = 'child'){
        super(name); //要覆盖父类哪个参数,就传递哪个参数名;如果不传,则默认全部使用父类参数的默认值
        // 如果子类还要增加自己的属性,一定要放在super之后
        this.type = 'child';
    }
}

console.log('继承', new Child('hello'));

getter 和 setter

class Parent{
  constructor(name = 'zk'){
    this.name = name;    
  }
  // getter
  // 注意,get后面的是属性,不是方法!
  get longName(){
    return 'mk' + this.name
  }
  // setter
  set longName(value){
    this.name = value
  }
}

// get操作
let v = new Parent();
console.log('getter',v.longName);
// set操作
v.longName = 'hello'
console.log('setter',v.longName);

静态方法 的定义和使用

// static只是用来定义静态方法,不是用来定义静态属性用的!
// 静态属性是直接在类上定义的。
class Parent{
  constructor(name = 'zk'){
    this.name = name;
  }
  static tell(){
    console.log('tell')
  }
}

Parent.tell();  // tell

静态属性的定义和使用(静态属性没有关键词(暂时没有,虽然有提案))

class Parent{
  constructor(name = 'zk'){
    this.name = name;
  }
  static tell(){
    console.log('tell')
  }
}

Parent.type = 'type'; // 直接在类上定义属性,不用new实例
console.log('静态属性', Parent.type);

思考:什么样的场景下适合用静态方法,什么样的场景下适合用静态属性?

Promise
什么是异步 | Promise的作用 |  Promise的基本用法

现在的js异步编程的方式有一下几种:
1.回调函数 2.事件监听 3.发布/订阅 4.Promise 对象 5.Generator

之前有两种方式:通过回调函数、通过触发方法
而现在的Promise更适合异步操作

// 通过模拟ajax请求,来使用promise实现异步操作
{
  let ajax = function(){
    console.log('执行1');
    return new Promise(function(resolve, reject){
      setTimeout(function(){
        resolve()
      }, 1000)                            
    })
  }

  ajax().then(function(){
    console.log("然后执行2");
  })
}

如果是多个异步操作呢?

// 在then()中继续使用promise即可
{
  let ajax=function(){
    console.log('执行3');
    return new Promise(function(resolve, reject){
      setTimeout(function () {
        resolve()
      }, 1000);
    })
  };

  ajax().then(function(){
    return new Promise(function(resolve, reject){
      setTimeout(function () {
        resolve()
      }, 2000);
    });
  }).then(function(){
    console.log('timeout3');
  })
}

Promise 提供 then 方法加载回调函数,catch方法捕捉执行过程中抛出的错误。catch捕获出错信息,err就是具体的出错信息

{
  let ajax=function(num){
    console.log('执行4');
    return new Promise(function(resolve,reject){
      if(num>5){
        resolve()
      }else{
        throw new Error('出错了')
      }
    })
  }

  ajax(6).then(function(){
    console.log('log',6);
  }).catch(function(err){
    console.log('catch',err);
  });
  
  ajax(3).then(function(){
    console.log('log',3);
  }).catch(function(err){
    console.log('catch',err);
  });
}

promise的高级用法
promise.all promise.race(赛跑)

情景1:保证所有图片都加载完成,再显示页面

  {
  // 所有图片加载完再添加到页面
  function loadImg(src){
    return new Promise((resolve,reject)=>{
      let img=document.createElement('img');
      img.src=src;
      img.onload=function(){
        resolve(img);
      }
      img.onerror=function(err){
        reject(err);
      }
    })
  }

  function showImgs(imgs){
    imgs.forEach(function(img){
      document.body.appendChild(img);
    })
  }

  Promise.all([
    loadImg('http://i4.buimg.com/567571/df1ef0720bea6832.png'),
    loadImg('http://i4.buimg.com/567751/2b07ee25b08930ba.png'),
    loadImg('http://i2.muimg.com/567751/5eb8190d6b2a1c9c.png')
  ]).then(showImgs)

}

情景2:同一个资源有多个来源,只要一个来源请求成功,就添加到页面,其他的忽略不管(先到先得)

{
  // 有一个图片加载完就添加到页面
  function loadImg(src){
    return new Promise((resolve,reject)=>{
      let img=document.createElement('img');
      img.src=src;
      img.onload=function(){
        resolve(img);
      }
      img.onerror=function(err){
        reject(err);
      }
    })
  }

  function showImgs(img){
    let p=document.createElement('p');
    p.appendChild(img);
    document.body.appendChild(p)
  }

  Promise.race([
    loadImg('http://i4.buimg.com/567571/df1ef0720bea6832.png'),
    loadImg('http://i4.buimg.com/567751/2b07ee25b08930ba.png'),
    loadImg('http://i2.muimg.com/567751/5eb8190d6b2a1c9c.png')
  ]).then(showImgs)

}

Iterator 和 for...of 循环

什么是Iterator接口 | Iterator的基本用法 | for...of 循环

for...of只能遍历数组,如果其他数据格式也想通过for...of来遍历,前提条件是它本身自带Iterator接口,如果没有,则需要为其自定义Iterator接口

数组支持遍历,因为它本身已经封装好了Iterator接口:

// arr本身有一个Iterator的方法,通过arr[Symbol.iterator]()可以触发,这个方法会返回一个对象,对象有一个next()方法,用来输出每次遍历的结果
{
  let arr=['hello','world'];
  let map=arr[Symbol.iterator]();
  console.log(map.next());
  console.log(map.next());
  console.log(map.next());

  结果:
  Object{value: 'hello', done: false}
  Object{value: 'world', done: false}
  Object{value: undefined, done: true}
}

如果其他没有Iterator接口的数据类型,也想实现遍历,则可以自定义Iterator接口

{
  let obj={
    start:[1,3,2],
    end:[7,9,8],
    [Symbol.iterator](){
      let self=this;
      let index=0;
      let arr=self.start.concat(self.end);
      let len=arr.length;
      return {
        // 注意:这里要返回一个对象,对象中包含next()方法,而且返回值有两个,value和done
        next(){
          if(index<len){
            return {
              // 注意每遍历一次,index都要+1
              value:arr[index++],
              done:false
            }
          }else{
            return {
              value:arr[index++],
              done:true
            }
          }
        }
      }
    }
  }
  // 本身for...of只能用来遍历数组,但通过为obj自定义了Iterator接口,从而使for...of也可以遍历obj
  for(let key of obj){
    console.log(key);
  }
}

说到for...of,就来回顾一下js中,数据的操作方法 和 几个循环的区别:

数组的操作方法:
会改变原数组的有:pop push unshift shift splice reverse sort
不会改变原数组的有:indexOf lastIndexof concat slice

其他常用操作方法:
es5的有:forEach filter map some every reduce
es6的有:find includes

几个循环方法及其区别:
forEach、for、for...in、for..of

1、for (编程式)

let arr = [1,2,3,4,5];
for(let i = 0;i<arr.length;i++){
  console.log(arr[i]);
}

2、forEach (声明式:不关心如何实现)

// 不支持return,它会遍历到结束
let arr = [1,2,3,4,5];
arr.forEach(function(item){
  console.log(item);
})

3、for...in (平时不用)

支持return,但一般不用它遍历数组
1- 因为索引不是数字,是字符串;
2- 因为for...in还会遍历出数组的私有属性,会影响length的准确性
let arr = [1,2,3,4,5];
arr.b = '100';
for(let key in arr){
  // key会变成字符串类型
  console.log(key) // 会把私有属性b也遍历出来
}

4、for...of (es6语法)

// 用于遍历数组
// 既可以return,又不会遍历出私有属性
let arr = [1,2,3,4,5];
for(let value of arr){
  console.log(value);
}

// 如果想让for...of遍历对象,不仅可以通过Iterator添加接口的方式,也可以通过Object.keys()来变通实现
let obj = {
  school: 'zk',
  age: 9
}
for(let value of Object.keys(obj)){
  console.log(obj[value])
}

filter的用法:

是否操作原数组:不操作
返回结果:过滤后的新数组
回调函数返回结果:如果返回true,则将这一项放入新数组中

// 如果想筛选(删除)某些项,可以用filter
{
  let arr = [1,2,3,4,5];
  let newArr = arr.filter(function(item){
    return item>2 && item<5;
  });
  console.log(newArr); // [3,4]
}

map(映射)的用法:

将原有的数组映射成一个新的数组

是否操作原数组:不操作
返回结果:映射后的新数组
回调函数返回结果:回调函数中返回什么,这一项就是什么

// 可以用于将json返回值,拼接成dom元素,即更新某些项,可以用map
{
  let arr1 = [1,2,3];
  let newArr1 = arr1.map(function(item){
    return `<li>${item}</li>`
  });
  console.log(newArr1.join('')); // <li>1</li><li>2</li><li>3</li>
}

includes的用法:

看数组中是否包含某个值

是否操作原数组:不操作
返回结果:判断结果布尔值
回调函数返回结果:包含则返回true,否则返回false

{
  let arr3 = [1,2,3,4,5,6];
  console.log(arr3.includes(5));
}

find的用法:

找到数组中的某个值

是否操作原数组:不操作
返回结果:判断找到的值
回调函数返回结果:回调中返回true,表示找到了,找到后即停止遍历;找不到返回undefined

{
  let arr4 = [1,2,3,4,5,55,6];
  let result = arr4.find(function(item, index){
    return item.toString().indexOf(5) > -1
  })
  console.log(result);
}

some的用法:

找到true后停止,返回true ;类似 “或”

是否操作原数组:不操作
返回结果:判断数组中是否存在某值符合条件
回调函数返回结果:判断有一个条件成立(或存在某值),就返回true,并立即停止

{
  let arr4 = [1,2,3,4,5];
  let result = arr4.some(function(item, index){
    return item.toString().indexOf(5) > -1
  })
  console.log(result);
}

every的用法:

找到false后停止,返回false;类似 “与”

是否操作原数组:不操作
返回结果:判断数组中是否所有的值都符合条件
回调函数返回结果:判断一旦有一个条件不符合(或不存在某值),就返回false,并立即停止

{
  let arr4 = [1,2,3,4,5,55,6];
  let result = arr4.every(function(item, index){
    return item.toString().indexOf(5) > -1
  })
  console.log(result);
}

reduce(翻译:收敛,使还原,使变弱)

有四个参数(prev, next, index, item)
用法:将上一次的结果作为下一次的第一个,继续和后面的数据进行操作
即1和2的结果,与3操作,他们的结果再与4操作,以此类推

是否操作原数组:不操作
返回结果:返回叠加后的结果
回调函数返回结果:返回回调函数返回的结果
`

{
  let arr4 = [1,2,3,4,5];
  //第一次 prev:代表数组的前一项;next:代表数组的第二项
  //第二次 prev:是数组的上一次返回值;next:是数组的第三项
  //如果没有返回值,则第二次的prev便是undefined
  let reduce = arr4.reduce(function(prev, next, index, item){
    // item为原数组
    console.log(arguments);
    // return 100;
  })
  console.log(reduce );
}

应用举例:
1、计算多个商品的单价和数量的价格总和;
2、将二维数组,变成一维数组;

// 应用1:
let sum2 = [{price:30,count:2},{price:30,count:3},{price:30,count:4} ].reduce(function(prev, next){
  console.log(prev, next)
  return prev+next.price*next.count 
}, 0); // 这个0,是默认执行第一次的prev(即默认第一项的值)

// reduce有两个参数:第一个是方法,第二个是默认第一项
// 应用2:
let arrs = [[1,2,3],[4,5,6],[7,8,9]];
let flat = arrs.reduce(function(prev,next){
  return prev.concat(next);
});
console.log(flat); // [1,2,3,4,5,6,7,8,9] 

Generator(发生器)

异步编程解决方案(相对promise更高级)
基本概念 | next函数的用法 | yield*的用法

Generator是什么?
举例来说,读取文件的协程写法如下。

functionasnycJob(){
  // ...其他代码
  var f=yield readFile( fileA);
  // ...其他代码
}

上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。

协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。

以上举例转载自:https://www.jianshu.com/p/56ec5c113f1b,如要了解关于Generator,可参看。

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

Generator包含next和yield,一旦步骤遇到yield或return,就停止,遇到next就进行下一步,直到结束

Generator函数定义:

// 需要babel-polyfill支持
{
  // genertaor基本定义
  let tell=function* (){
    yield 'a';
    yield 'b';
    return 'c'
  };

  let k=tell();

  console.log(k.next());
  console.log(k.next());
  console.log(k.next());
  console.log(k.next());
}

为其他数据结构部署Iterator接口,实现遍历,不光可以按照之前Iterator接口手动实现的写法,还可以使用Generator。

Generator就是一个遍历器生成接口

Generator 应用,实现遍历器接口(一种应用情景)
{
  let obj={};
  obj[Symbol.iterator]=function* (){
    yield 1;
    yield 2;
    yield 3;
  }

  for(let value of obj){
    console.log('value',value);
  }
}

Generator最大的优势 : 状态机
比如一个对象只有三种状态,A,B,C ,他们会在这三种状态中循环出现。

{
  let state = function* (){
    // 因为一直都是true,所以会一直循环
    while(1){
      yield 'A';
      yield 'B';
      yield 'C';
    }
  }
  let status = state();
  console.log(status.next());// Object {value: 'A',  done: false}
  console.log(status.next());// Object {value: 'B',  done: false}
  console.log(status.next());// Object {value: 'C',  done: false}
  console.log(status.next());// Object {value: 'A',  done: false}
  console.log(status.next());// Object {value: 'B',  done: false}
  ...
}

async...await 语法是Generator函数的一个语法糖

{
  let state=async function (){
    while(1){
      await 'A';
      await 'B';
      await 'C';
    }
  }
  let status=state();
  console.log(status.next());
  console.log(status.next());
  console.log(status.next());
  console.log(status.next());
  console.log(status.next());
}

Generator的应用:
1、剩余抽奖次数逻辑

{
  let draw=function(count){
    //具体抽奖逻辑
    console.info(`剩余${count}次`)
  }

  let residue=function* (count){
    while (count>0) {
      count--;
      yield draw(count);
    }
  }

  let star=residue(5);
  let btn=document.createElement('button');
  btn.id='start';
  btn.textContent='抽奖';
  document.body.appendChild(btn);
  document.getElementById('start').addEventListener('click',function(){
    star.next();
  },false)
}

2、长轮巡

{
  // 长轮询
  let ajax=function* (){
    yield new Promise(function(resolve,reject){
      //模拟每200毫秒请求一次
      setTimeout(function () {
        resolve({code:0})
      }, 200);
    })
  }

  let pull=function(){
    // generator实例
    let genertaor=ajax();
    let step=genertaor.next();
    // step.value就是Promise实例
    step.value.then(function(d){
      if(d.code!=0){
        setTimeout(function () {
          console.info('wait');
          pull()
        }, 1000);
      }else{
        console.info(d);
      }
    })
  }

  pull();
}

Decorator(修饰器)[针对类]

基本概念 :
修饰器是一个函数,用来修改类的行为(或扩展类的功能)

使用准备:
1、除了babel-polyfill,还需要安装一个插件包:
babel-plugin-transform-decorators-legacy

2、修改.babelrc文件
{ "presets":["es2015"], "plugins":["transform-decorators-legacy"] }
3、再重启服务

基本用法:
声明函数体:

{
  // 声明一个修饰器
  let readonly = function(target, name, descriptor){
    descriptor.writable = false; //描述是否可写
    return descriptor
  }
}

使用(在类的里面):

{
  class Test{
    @readonly
    time(){
      return '2018-03-09'
    }
  }
}

let test = new Test();
// 尝试修改类
test.time = function(){
  console.log('reset time');
}

console.log(test.time());

//会报错提示无法修改只读属性time
Uncaught TypeError:  Cannot assign to read only property 'time' of object '#<Test>'

在类的外面使用:

{
  let typename=function(target,name,descriptor){
    target.myname='hello'; // target指的就是类本身
  }

  @typename
  class Test{

  }

  console.log('类修饰符',Test.myname);
  // 第三方库修饰器的js库:core-decorators; 
  // 可以通过npm安装:npm install core-decorators
  // import 进来以后,直接就可以@使用
}

Decorator的应用:
做埋点,日志统计
把埋点系统抽离出来,成为一个可复用的模块

{
  let log=(type)=>{
    return function(target,name,descriptor){
      let src_method=descriptor.value;
      descriptor.value=(...arg)=>{
        src_method.apply(target,arg);
        console.info(`log ${type}`);
      }
    }
  }

  class AD{
    @log('show')
    show(){
      console.info('ad is show')
    }
    @log('click')
    click(){
      console.info('ad is click');
    }
  }

  let ad=new AD();
  ad.show();
  ad.click();
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容