HolyZheng / holyZheng-blog

草稿
36 stars 0 forks source link

前端基础——JavaScript篇 #3

Open HolyZheng opened 6 years ago

HolyZheng commented 6 years ago

作者:holyZheng 转载请注明出处

DOM

严格说DOM并不是JavaScript本身的一部分,但是还是在此列出总结。

DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口)。DOM描述了一个层次化的节点树,允许开发人员添加,移除和修改页面的某一部分。

节点层次

DOM可以将任何html或xml文档描绘成一个由多层节点构成的结构。节点分为几种不同的类型。 常见类型:

1. node类型

JavaScript中所有节点类型都继承自node类型,所以都共享着相同的基本属性和方法,比如

好像写得太细。。赶紧结个尾先。。

总结

DOM是针对HTML和XML文档的一个API(应用程序编程接口),将任何html或xml文档描绘成一个由多层节点构成的结构,允许开发人员添加,移除和修改页面的某一部分。比如一些反映节点关系的api,操作节点的api,查看文档信息的api,创建节点的api,查询节点的api等。

HolyZheng commented 6 years ago

执行环境,作用域链,变量提升,闭包,事件模型

执行环境(对应于作用域)

执行环境定义了变量或函数有权访问的其他数据,决定了他们的行为。每个执行环境都有一个与之对应的变量对象,环境中定义的所有变量和函数都包含在这个对象中,比如web浏览器中,全局执行环境对应的对象就是window。

ps:执行环境对应于作用域,作用域它表明了变量的可访问范围,比如全局作用域,局部作用域。

作用域链

执行环境是用一个环境栈来管理的,栈的最底部就是我们的全局作用域,在执行代码的时候,每遇到一个新的执行环境,就把它推进栈中,栈的最顶部就是当前的执行环境。 作用域链,在函数调用的时候会给该函数创建一个作用域链,作用域链第一位为当前函数执行环境对应的变量对象,第二位对应该函数的外部函数的执行环境对应的变量对象,直到最后以为对应全局执行环境的变量对象,它的作用在于保证执行环境对有权访问的变量和函数的有序访问。

延长作用域链的方法

try catch语句的catch模块,它会在作用域链的顶端临时增加一个变量对象,并在代码执行后移除。

变量提升

JavaScript在编译阶段会把变量和函数的声明放入内存中,所有我们可以在代码中函数声明前正常使用函数,在变量声明前使用变量不会报错(值为undefined)。 ps: es6引入的const和let不存在变量提升。

上下文

是指函数this的指向,如当函数作为对象的方法被调用时,this 被设置为调用方法的对象;通过new的操作符创建一个对象的实例,this 的值将被设置为新创建的实例

闭包

闭包是指有权访问另一个函数作用域中变量的函数,常见的闭包创建方式是在一个函数内部创建另一个函数。

function wrap () {
var data = 'something'
return function() {
console.log(data)
}
}
var  fun = wrap()
fun() // 'something'

闭包产生的原因是内部函数的作用域链中包含了外部函数的作用域。 如何释放闭包中变量占用的内存呢

fun = nul

解除对匿名函数的引用,以便释放内存。

闭包的应用场景

  1. 作为访问私有变量和私有方法的共有方法
    function obj () {
    var one = 1;
    var two = 2;
    return {
    getOne:  function () {
    // 访问私有变量one
    return one;
    },
    getTwo: function () {
    // 访问私有变量two
    return two;
    }
    }
    }
  2. 单例模式
    function obj(data) {
    // ....
    }
    // 利用闭包实现单例模式,确保obj类只有一个实例
    function singleton (data) {
    var instance;
    return function () {
    if (!instance) {
    instance = new obj(data)
    }
    return instance
    }
    }

    事件模型

    事件模型(事件流)分为三个阶段 事件捕获 -> 目标阶段 -> 事件冒泡

  3. 事件捕获的思想就是:不太具体的外层节点应该更早的接收到事件,而最具体的目标节点最后接收到事件;事件的触发顺序是从外层节点向内层具体目标节点传递。
  4. 事件冒泡则是事件由最具体的目标元素开始,逐级向上传播到外层不具体的节点上去。
  5. 目标阶段就是事件的触发了。

    事件冒泡的利用:事件委托

    在JavaScript中,添加到页面的事件处理程序的数量直接关系到页面的整体性能,绑定的事件处理程序越多,对性能的影响越大。事件委托就是一个解决“事件处理程序过多”的方案。 具体实现:在dom树中尽可能高的层次上添加事件处理程序,在事件冒泡阶段处理在子节点中触发的事件。 event.target指向触发事件的子节点,event.currenttarget指向事件处理程序绑定的元素。

    <ul id="list">
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
    ......
    <li>item n</li>
    </ul>
    // 给父层元素绑定事件
    document.getElementById('list').addEventListener('click', function (event) {
    // ie6-8  event.srcElement;
    var target = event.target
    // 判断是否匹配目标元素
    if (target.nodeName.toLocaleLowerCase === 'li') {
    console.log('the content is: ', target.innerHTML);
    }
    });
    // 阻止事件冒泡
    function stopPropagation(e) {
    var e = e || window.event;
    if ( e && e.stopPropagation ){
    e.stopPropagation();
    }else{
    e.cancelBubble = true;
    }
    }
    // 阻止默认行为
    function stopDefault(e) {
    var e = e || window.event;
    if (e && e.preventDefault){
    e.preventDefault();
    }else{
    e.returnValue = false;
    }
    return false;
    }
    
    //跨浏览器事件处理程序
    var eventUtil = {
    // 添加句柄
    addHandler: function(element, type, handler){
    if(element.addEventListener){
    element.addEventListener(type, handler, false);
    }else if(element.attachEvent){
    element.attachEvent('on' + type, handler);
    }else{
    element['on' + type] = handler;
    }
    },
    // 删除句柄
    removeHandler: function(element, type, handler){
    if(element.removeEventListener){
    element.removeEventListener(type, handler, false);
    }else if(element.detachEvent){
    element.detachEvent('on' + type, handler);
    }else{
    element['on' + type] = null;
    }
    }
    };

var oBtn = document.getElementById('btn'); function evtFn(){ alert('hello world'); } eventUtil.addHandler(oBtn, 'click', evtFn); eventUtil.removeHandler(oBtn, 'click', evtFn);

### 深浅拷贝
> ps: 趁热理解浅拷贝和深拷贝 对于原始类型如字符串,深浅拷贝都是是对值的复制,对于引用类型如对象来说,浅拷贝是对对象地址的复制,深拷贝则是对整个对象的拷贝。
[es5的数据类型](https://segmentfault.com/a/1190000012808752)
JavaScript有5种基本类型:
undefined null boolean string number  es6: Symbol
4种引用类型:
Object Array Data RegExp  es6: Set Map

typeof 返回的值有
```js
typeof {}
"object"
typeof []
"object"
typeof null
"object"
typeof undefined
"undefined"
typeof function a () {}
"function"
typeof 'abc'
"string"
typeof true
"boolean"
typeof 1
"number"
typeof new Date()
"object"

浅拷贝

let a = {
    age: 1
}
let b = Object.assign({}, a)
// let b = {...a}
a.age = 2
console.log(b.age) // 1

深拷贝

let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
// 但是该方法会忽略掉函数和`undefined`
// 递归实现
 let cloneObj = function (obj) {
   if (typeof obj !== 'object') {
     return;
   } else {
     let newobj = obj.constructor === Array ? [] : {};
     for (var i in obj) {
       newobj[i] = typeof obj[i] === 'object' ? cloneObj(obj[i]) : obj[i]
     }
     return newobj;
   }
 }
HolyZheng commented 6 years ago

JavaScript面向对象编程

ps: 面向对象三大特征: 封装,继承,多态。可通过闭包实现私有变量,私有方法,共有方法来实现封装,继承方法很多,es6才引进class/extends关键字来实现类和继承。js本身是一个动态的语言,本身支持多态。可以通过判断参数数量进行不同操作来模拟函数重载

面向对象的语言都有一个标准——他们都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象。但是JavaScript本身没有类的概念,所以在JavaScript的对象与其他基于类的语言的对象不一样,在JavaScript中,对象的定义是:属性的集合,其中的属性可以是基本值,对象或者函数。

创建对象的方法

1. 字面量的方式

var obj = {
  name: 'xx',
  age: 12
}

2. 构造函数

function Obj () {
  this.name = 'xx',
  this.age = 12,
  this.fun = function(){} 
}
var objOne = newObj()

3. 原型模式

function Obj () {  }
Obj.prototype.name = 'xx'
Obj.prototype.age = 12

4. 构造函数和原型模式结合

function Obj(arg) {
  this.data = arg
}
Obj.prototype.fun = function () { // ... }

5. 工厂函数

function createObj (data) {
   var obj = {}
   obj.somedata = data
   return obj
}

JavaScript的继承

1. 原型链继承

原型链继承就是将父类的实例赋值给子类的原型。

// 父类
function SuperType () {}
// 子类
function Sub () {}
// 继承
Sub.prototype = new SuperType()

问题:通过继承得到的属性和方法会在子类的所有实例之间共享

2. 借用构造函数实现继承

在子类构造函数的内部使用子类构造函数的执行上下文调用父类的构造函数

// 父类
function SuperType () {}
// 子类
function Sub () {
// 继承 
  SuperType.call(this)
}

问题:父类原型中的方法对子类是不可见的,只能继承构造函数中定义的属性,所有继承过来的属性,在子类创建实例的时候都会重新创建一遍。

3. 组合继承

组合继承就是原型链继承和借用构造函数继承的组合,通过原型链继承来继承来自父类原型的属性,通过借用构造函数的方式来继承来自父类构造函数的属性。

function SuperType () {}
function Sub () {
  SuperType.call(this)
}
Sub.prototype = new SuperType()
Sub.prototype.constructor = Sub

问题:子类的原型与实例中存在着相同的属性。

4. 原型式继承

先创建一个临时的构造函数,然后将传入的对象作为这个临时构造函数的原型,最后返回这个临时构造函数的实例。与 Object.create()的作用相似。

function object (o) {
  function F () { }
  F.prototype = o
  return new F()
}

问题:子类实例之间存在属性共享的问题,子类和父类之间是一个浅复制的关系。

5. 寄生式继承

创建一个仅用于封装继承过程的函数,函数内部动态的给函数添加属性。

function object(o) {
  var sub = Object.create(o)
  sub.prototype.xxx = xxx
  return sub
}

问题子类之间有属性共享的问题,子类和父类之间是浅复制关系

6. 寄生组合式继承

先创建一个空对象,然后父类构造函数的原型赋值给空对象的原型,再将空对象赋值给子类构造函数的原型

function SuperType () {}
function Sub () {
  SuperType.call(this)
}
// 过滤掉父类构造函数中的属性
Sub.prototype = Object.create(SuperType.prototype)
Sub.prototype.constructor = Sub

最完美的继承方式

ES6 class 与 extends

class

ES6引进了class关键字,但是它只是语法糖,只是让对象的写法更加清晰更像面向对象的语法,如:


// es5
function Point (x,y) {
this.x = x
this.y = y
}
Point.prototype.doSomething = function () {}

// es6 class Point { constructor (x, y) { this.x= x this.y = y } doSomething() {} }

typeof Point === Function Point === Point.prototype.constructor

#### 注意:
1. es6内部定义的方法是不可枚举的,如disomething()
2. 类必须使用new运算符,不能直接运行
3. 不存在变量提升
#### 只是语法糖
```js
// es6
class Point {
  constructor (x, y) {
    this.x= x
    this.y = y
  }
  doSomething() {}
}

等同于,全部定义在原型上

Point.prototype = {
  constructor (x, y) {
    this.x= x
    this.y = y
  }
  doSomething() {}
}

extends

es6通过extends关键字实现继承,这比es5通过原型实现继承的写法要清晰和方便得多。

class ColorPoint extends Point {
  constructor (x, y, color) {
    super(x, y)  // 调用父类构造函数
    this.color = color
  }
  doSomething() {}
}

extends继承的效果等同于es5中的寄生组合式继承

super 关键字

  1. 作为函数,代表父类构造函数,只能用在子类构造函数中。
  2. 作为对象,指向父类得原型对象。

    原型链

    ColorPoint.__proto__ === Point
    ColorPoint.prototype.__proto__ === Point.prototype

    static

    可通过static关键字定义静态成员

    class Obj {
    constructor(){}
    static doSomething(){ console.log('static') }
    }
    Obj.doSomething()  // 'static'

    get/set

    可以直接重写某个属性的getter和setter。

    
    class Car {
    
    constructor (name) {
    this._name = name;
    } 
    
    set name (name) {
    this._name = name;
    }
    
    get name () {
    return this._name;
    }

} let car = new Car('Tesla'); console.log(car.name); // Tesla car.name = 'BMW'; console.log(car.name); // BMW

HolyZheng commented 6 years ago

这篇文章主要 解决的问题 是:什么是__proto__?什么是prototype?他们的关系是什么?在原型链中扮演什么角色?

proto和prototype

  1. prototype是函数的一个属性,在定义构造函数的时候自动创建,它指向函数的原型,被 __proto__指向。这个原型对象里包含着自定义的方法属性。
  2. __proto__是对象的内部属性,它指向构造器的prototype,对象依赖它来进行原型链的查询,instanceof方法也是依赖它来判断是否存在继承关系。
  3. prototype只能作为构造函数的属性,而__proto__可以作为任意对象的属性。

proto、prototype和原型链之间的联系

看一段代码:

 function Foo(name) { this.name = name };
 Foo.prototype.age = 18;
 let student = new Foo("holy");

我们通过一张图来表示这段代码构建的原型链: 这里写图片描述 这张图中原型链可分成两部分:

student ---> Foo.prototype ---> Object.prototype ---> null
Foo ---> Function.prototype ---> Object.prototype ---> null

我们从对象student看起:根据 概念3 , prototype 只能作为构造函数的属性,proto只能作为对象的属性。所以对象sutdent只有proto,没有prototype。

student.prototype;
undefined

student对象是Foo构造函数的一个实例,根据 概念2 它的proto指向Foo.prototype

student.__ptoto__ == Foo.prototype;
true

以为 Foo.prototype 是一个对象,所以 Foo.prototype.proto指向 Object.prototype

Foo.prototype.__proto__ == Object.prototype;
true

再来看第二部分: Foo 是一个函数,所以 Foo.proto指向 Function.prototype

Foo.__proto__ == Function.prototype
true

再 JavaScript 中函数都是第一公民,而且函数也是对象,所以Function.prototype.proto指向 Object.prototype

Function.prototype.__proto__ == Object.prototype
true

原型链的工作流程

  1. student.name 在student中找到 name属性,返回“holy”;
  2. student.age 在student中未找到,于是通过student.__proto__在Foo.prototype中找到,返回 18;
  3. student.toString() 在student中未找到,接着通过student.__proto在Foo.prototype中寻找,未找到,继续通过Foo.prototype.__proto在Object.prototype中寻找,找到了,返回‘[object object]’。
  4. student.getWeight() 依次在student、Foo.prototype、Object.prototype中寻找,均未找到,抛出错误。
HolyZheng commented 6 years ago

改变this指向的方法

bind()

bind()方法,返回一个绑定函数,第一参数作为绑定函数的执行上下文,其他参数会在绑定函数执行的时候置于其他实参之前传递给绑定函数。

var pokemon = {
    firstname: 'Pika',
    lastname: 'Chu ',
    getPokeName: function() {
        var fullname = this.firstname + ' ' + this.lastname;
        return fullname;
    }
};

var pokemonName = function() {
    console.log(this.getPokeName() + 'I choose you!');
};

var logPokemon = pokemonName.bind(pokemon); 
logPokemon(); 
// 'Pika Chu I choose you!'

call()

call()方法接收多个参数,第一个参数作为函数this的指向,其他参数作为函数的参数,并且不会返回新的函数,而是直接执行函数。

apply()

apply()方法和call类似,不过apply()方法只有两个参数,第一个为函数的this指向,第二个为一个数组,数组的元素作为函数的参数,跟call一样会立即执行这个函数

var pokemon = {
    firstname: 'Pika',
    lastname: 'Chu ',
    getPokeName: function() {
        var fullname = this.firstname + ' ' + this.lastname;
        return fullname;
    }
};

var pokemonName = function(snack, hobby) {
    console.log(this.getPokeName() + ' loves ' + snack + ' and ' + hobby);
};

pokemonName.call(pokemon,'sushi', 'algorithms'); // Pika Chu  loves sushi and algorithms
pokemonName.apply(pokemon,['sushi', 'algorithms']); // Pika Chu  loves sushi and algorithms

模拟实现

Function.prototype.myCall = function (ctx) {
  var ctx = ctx || window
  // 给 ctx 添加一个属性
  ctx.fn = this
  // 将 ctx 后面的参数取出来
  var args = [...arguments].slice(1)
  var result = ctx.fn(...args)
  // 删除 fn
  delete ctx.fn
  return result
}
Function.prototype.myApply = function (ctx) {
  var ctx = ctx || window
  ctx.fn = this
  var result
  if (arguments[1]) {
    result = ctx.fn(...arguments[1])
  } else {
    result = ctx.fn()
  }

  delete ctx.fn
  return result
}
Function.prototype.myBind = function (ctx) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var _this = this
  var args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(ctx, args.concat(...arguments))
  }
}
HolyZheng commented 6 years ago

new 一个对象的过程

function Person() { 
  this.name = 'holz' 
} 
let p = new Person();
  1. 创建一个空对象 let obj = {}
  2. 修改空对象指向的原型,objproto = Person.prototype
  3. 使用空对象作为上下文调用构造函数 Person.call(obj),来设置空对象的属性。
  4. 返回这个对象。
HolyZheng commented 6 years ago

数组Array

创建数组的方法:

let arr = new Array()  // new Array('a', 'b', 'c')
let arr = [] // ['a', 'b', 'c']

concat(), every(), filter(), forEach(), indexOf(), isArray(), map(), pop(), push(), reduce(), reverse(), some(), sort(), splice(), toString(), unshift(), slice() 会对原数组造成影响的有: pop(), push(), reverse(), splice(), sort(), shift(), unshift(),

  1. concat() 用于连接两个数组,不会影响,而是返回一个被连接数组的副本

    arrA.concat(arrB)
    arrA.concat(arrB, arrC)
    arrA.concat('a', 'b', 'c')
  2. every() 检查是否所以成员都符合条件

    var ages = [32, 33, 16, 40];
    arges.every(age => {
    return age > 18
    })
    // false
  3. forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。不影响原数组

  4. isArray(),判断一个对象是否是数组

    Array.isArray('sdadas')
    // false
    Array.isArray([])
    // true
    Array.isArray({length:0})
    // false
  5. join() 方法用于把数组中的所有元素转换一个字符串。默认分隔符是 逗号 不影响原数组

    var fruits = ["Banana", "Orange", "Apple", "Mango"];
    var energy = fruits.join();
    // Banana,Orange,Apple,Mango 
  6. map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。不影响原数组

    let arr = [1, 2, 3, 4]
    arr.map(item => {
    return item > 2 ? item : 0
    })
    // [0, 0, 3, 4]
  7. reduce(),法对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。

    
    const array1 = [1, 2, 3, 4];
    const reducer = (accumulator, currentValue) => {
    console.log(accumulator,currentValue); 
    return accumulator + currentValue
    };

// 过程 array1.reduce(reducer) 1 2 3 3 6 4

// 1 + 2 + 3 + 4 console.log(array1.reduce(reducer)); // expected output: 10

// 5 + 1 + 2 + 3 + 4 console.log(array1.reduce(reducer, 5)); // expected output: 15

8. slice(),选取数组的的一部分,并返回一个新数组。**不影响原数组**
```js
var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
var citrus = fruits.slice(1,3);
// Orange,Lemon
  1. splice() 方法用于插入、删除或替换数组的元素。返回一个数组包含被删除的数组成员,会对原数组造成影响

    var fruits = ["Banana", "Orange", "Apple", "Mango"];
    fruits.splice(2,0,"Lemon","Kiwi");
    // Banana,Orange,Lemon,Kiwi,Apple,Mango

    splice

  2. sort(),方法用于对数组的元素进行排序 对原数组有影响

    
    // 默认排序顺序是根据字符串Unicode码点
    var fruits = ["Banana", "Orange", "Apple", "Mango"];
    fruits.sort();
    // Apple,Banana,Mango,Orange

var points = [40,100,1,5,25,10]; points.sort() // [1, 10, 100, 25, 40, 5] points.sort((a,b) => a-b) // [1, 5, 10, 25, 40, 100]


### ES6
> 扩展运算符,Array.from(),Array.of(),find(),findIndex(),fill(),entries(),keys(),values(),includes()

1. 扩展运算符
```js
function add(x, y) {
  return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42

// 复制数组 es5和es6
const a2 = a1.concat();
const a2 = [...a1];

// 合并数组
[...a1, ...a2]

扩展运算符分为数组的扩展运算符和对象的扩展运算符,在数组方面可以将具有Iterator 接口的数据接口转成数组:比如

[...arguments]
[...'abcdef']
[...[1,2,3]]
[...NodeList]

在对象方面,可以提取对象的可遍历属性:

let obj = {a: 1, b: 2}
let a = { ...obj }
  1. Array.from(),Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
    
    let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
    };

// ES5的写法 var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法 let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

3. Array.of(),Array.of方法用于将一组值,转换为数组。
```js
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
  1. find() 找出第一个符合条件的数组成员,否则undefined;findIndex() 找出第一个符合条件的成员的下标,否则-1
    [1, 5, 10, 15].find(function(value, index, arr) {
    return value > 9;
    }) // 10
    [1, 5, 10, 15].findIndex(function(value, index, arr) {
    return value > 9;
    }) // 2
  2. fill(),填充数组,指定填充范围,没有范围则全部成员都被填充/覆盖。
    ['a', 'b', 'c'].fill(7, 1, 2)
    // ['a', 7, 'c']
  3. entries(),keys(),values()返回一个遍历器。
    
    for (let index of ['a', 'b'].keys()) {
    console.log(index);
    }
    // 0
    // 1

for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b'

for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"

7. includes()
```js
[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true
HolyZheng commented 6 years ago

本地存储 cookie,localStorage,sessionStorage,indexDB

cookie

localStorage

let name = localStorage.getItem('name')

localStorage.removeItem('name')

localStorage.clear()

### sessionStorage
- 页面关闭就清理
- 5 M容量
```js
// 保存数据到sessionStorage
sessionStorage.setItem('key', 'value');

// 从sessionStorage获取数据
var data = sessionStorage.getItem('key');

// 从sessionStorage删除保存的数据
sessionStorage.removeItem('key');

// 从sessionStorage删除所有保存的数据
sessionStorage.clear();

下面的示例会自动保存一个文本输入框的内容,如果浏览器因偶然因素被刷新了,文本输入框里面的内容会被恢复,因此写入的内容不会丢失。

// 获取文本输入框
var field = document.getElementById("field");

// 检测是否存在 autosave 键值
// (这个会在页面偶然被刷新的情况下存在)
if (sessionStorage.getItem("autosave")) {
  // 恢复文本输入框的内容
  field.value = sessionStorage.getItem("autosave");
}

// 监听文本输入框的 change 事件
field.addEventListener("change", function() {
  // 保存结果到 sessionStorage 对象中
  sessionStorage.setItem("autosave", field.value);
});

indexDB

service worker

Service Worker 简介 Service workers 本质上充当Web应用程序与浏览器之间的代理服务器。它目的在于创建更好的离线体验。

  1. 特点:
    • 独立的线程(有自己的上下文)
    • 持久存在,触发手动删除
    • 可拦截代理请求和返回,缓存文件
    • 可向客户端推送消息
    • 不能直接操作dom
    • 须在https下可用
    • 异步,大都通过promise实现。
  2. 生命周期:
    • 下载
    • 安装
    • 激活
  3. 例子
    
    // index.js
    if (navigator.serviceWorker) {
    navigator.serviceWorker
    .register("sw.js")
    .then(function(registration) {
      console.log("service worker 注册成功");
    })
    .catch(function(err) {
      console.log("servcie worker 注册失败");
    });
    }
    // sw.js
    // 监听 `install` 事件,回调中缓存所需文件
    self.addEventListener("install", e => {
    e.waitUntil(
    caches.open("my-cache").then(function(cache) {
      return cache.addAll(["./index.html", "./index.js"]);
    })
    );
    });

// 拦截所有请求事件 // 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据 self.addEventListener("fetch", e => { e.respondWith( caches.match(e.request).then(function(response) { if (response) { return response; } console.log("fetch source"); }) ); });

HolyZheng commented 6 years ago

对象属性遍历(不包括原型)

1. 遍历自身可枚举属性

使用for..in 迭代,并通过 hasOwnProperty进行筛选

for (let item in obj) {
// 过滤掉原型上的属性
  if (obj.hasOwnProperty(item)) {
    console.log(obj[item])
  }
}

2. 遍历自身不可枚举属性

使用 getOwnPropertyNames 进行迭代返回自身所有可枚举和不可枚举属性的名字,并筛选不通过 propertyIsEnumerable 方法检测的属性

let obj = function () {}
obj.prototype.addObj = 1

for (let item of Object.getOwnPropertyNames(obj)) {
  if (!obj.propertyIsEnumerable(item)) {
    console.log(obj[item])
  }
}

length 0
name obj
arguments null
caller null
prototype {addObj: 1, constructor: ƒ}

3. 遍历自身可枚举属性及不可枚举属性

通过 getOwnPropertyNames

for (let item of Object.getOwnPropertyNames(obj)) {
  console.log(obj[item])
}
HolyZheng commented 6 years ago

向后端传输数据的方法

1. Ajax

AJAX 是一种在无需重新加载整个网页的情况下,进行页面的局部更新的技术。主要通过XMLHttpRequest对象实现,它可以让我们通过JavaScript向服务器发起异步请求。

if (window.XMLHttpRequest) { requestObj = new XMLHttpRequest(); } else { requestObj = new ActiveXObject("Microsoft.XMLHTTP") }

requestObj.open("方法", url, async); requestObj.setRequestHeader("Content-Type", "xxxxxxxx") //根据方法设置相应的头 requestObj.send();

// 获取响应 requestObj.onreadystatechange = function() { if (requestObj.readyState == 4) { if (requestObj.status == 200) { console.log(requestObj.reponseText); } } }

/ 0 未初始化,即未调用open 1 启动,已经调用了open,但未调用send 2 发送,调用send 3 接受,接受到了部分数据 4 数据接受完成 /

### 2. Fetch
提供了一个 JavaScript接口,进行请求和响应
```js
// Example POST method implementation:

postData('http://example.com/answer', {answer: 42})
  .then(data => console.log(data)) // JSON from `response.json()` call
  .catch(error => console.error(error))

function postData(url, data) {
  // Default options are marked with *
  return fetch(url, {
    body: JSON.stringify(data), // must match 'Content-Type' header
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, same-origin, *omit
    headers: {
      'user-agent': 'Mozilla/4.0 MDN Example',
      'content-type': 'application/json'
    },
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, cors, *same-origin
    redirect: 'follow', // manual, *follow, error
    referrer: 'no-referrer', // *client, no-referrer
  })
  .then(response => response.json()) // parses response to JSON
}

3. 表单传输

<form action="http://foo.com" method="get">
  <div>
    <label for="say">What greeting do you want to say?</label>
    <input name="say" id="say" value="Hi">
  </div>
  <div>
    <label for="to">Who do you want to say it to?</label>
    <input name="to" id="to" value="Mom">
  </div>
  <div>
    <button>Send my greetings</button>
  </div>
</form>

当你提交表单的时候,您将看到

www.foo.com/?say=Hi&to=Mom

在浏览器地址栏里。 如果是post方法

POST / HTTP/1.1
Host: foo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

say=Hi&to=Mom
HolyZheng commented 6 years ago

判断变量类型的方法

typeof instanceof Array.isArray Object.prototype.toString.apply()

typeof

无法区分null,数组和对象

typeof {}
"object"
typeof []
"object"
typeof null
"object"
typeof undefined
"undefined"
typeof function a () {}
"function"
typeof 'abc'
"string"
typeof true
"boolean"
typeof 1
"number"
typeof new Date()
"object"

instanceof

一个变量在其原型链中是否存在某个构造函数的 prototype 属性 instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。

[] instanceof Array
true
[] instanceof Object
true
let obj = {}
undefined
obj instanceof Object
true
function f () {} 
undefined
f instanceof Object
true
f instanceof Function
true
null instanceof Object
false
undefined instanceof Object
false
'a' instanceof String
false
1 instanceof Number
false
true instanceof Boolean

Object.prototype.toString.apply

Object.prototype.toString.apply({})
"[object Object]"
Object.prototype.toString.apply([])
"[object Array]"
Object.prototype.toString.apply("str")
"[object String]"
Object.prototype.toString.apply(1)
"[object Number]"
Object.prototype.toString.apply(null)
"[object Null]"
Object.prototype.toString.apply(undefined)
"[object Undefined]"
function f () {} 
Object.prototype.toString.apply(f)
"[object Function]"

在确定不为null,undefined 的情况下

f.constructor === Function
true
[].constructor === Array
true
let obj = {}
obj.constructor === Object
true
let n = null
n.constructor === Object
/*
* VM810:1 Uncaught TypeError: Cannot read property 'constructor' of null
*    at <anonymous>:1:3
* (anonymous) @ VM810:1
*/
let un = undefined
un.constructor
/*
* VM93:1 Uncaught TypeError: Cannot read property 'constructor' of undefined
*     at <anonymous>:1:4
*/
"s".constructor === String
true
let num = 1;
num.constructor === Number
true
HolyZheng commented 6 years ago

es6

箭头函数

箭头函数表达式的语法比函数表达式更短,并且没有自己的this,arguments,super或 new.target。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。

关于this,箭头函数没有自己的this,他内部出现的this,指的是它定义时所在的上下文。

不应该使用箭头函数的情况:

  1. 不应该作为对象的属性。因为对象的属性方法的this应该指向该对象。
  2. 不能作为构造函数。因为构造函数的this需要指向具体实例,但是箭头函数在定义时就绑定了this的指向。
  3. 考虑可读性,多个箭头函数堆在一起可能会造成可读性下降。

数据结构 Set

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

const s = new Set();
const set = new Set([1, 2, 3, 4, 4]);

// 数组去重
let arr = [1,1,2,2,3,3]
let s = new Set(arr)
[...s]
// [1, 2, 3]

Set 结构的实例有以下属性。

Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

WeakSet

成员只能时对象

数据结构 Map

Map类似于对象,也键值对的集合,但是他的键可以是各种类型的值。

WeakMap

键名只能是对象。 它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。 只有 get()、set()、has()、delete()。

扩展运算符

  1. 复制数组
    const a1 = [1, 2];
    // 写法一
    const a2 = [...a1];
  2. 合并数组
    [...arr1, ...arr2]
  3. 可以将具有迭代器接口的对象转为数组 (Array,Map,Set,String,arguments,NodeList)
    let array = [...nodeList];

    如果没有迭代器接口,如类数组对象arrayLike,可以通过array.prototype.slice.call

    Array.prototype.slice.call(arrayLike)

    generator,async/await

    对应地址

promise

对应地址

HolyZheng commented 6 years ago

vue生命周期

vue生命周期 vue生命周期,官网生命周期图示

beforeCreate,create,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed

  1. beforeCreate 初始化事件和生命周期
  2. create 实例创建完成,可以访问自定义的数据和方法
  3. beforeMount 根据template,编译成渲染函数,渲染dom
  4. mounted 将dom挂载到实例上,可以进行dom操作
  5. beforeUpdata,update 当数据data被修改时,先后触发
  6. beforeDestroy 移除数据,子组件,事件监听
  7. destroyed 实例已摧毁