html与js的组合使用

# JSON

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。即 JavaScript 对象标记法。它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小 如:{"age":"12", "name":"samy"}

# 规则

  • 数组(Array)用方括号(“[]”)表示。
  • 对象(Object)用大括号(”{}”)表示。
  • 名称/值对(name/value)组合成数组和对象。
  • 名称(name)置于双引号中,值(value)有字符串、数值、布尔值、null、对象和数组。
  • 并列的数据之间用逗号(“,”)分隔

# eval

它的功能是把对应的字符串解析成JS代码并运行; 应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。 由JSON字符串转换为JSON对象的时候可以用eval,var obj = eval( "("+str + ")" );

# JSON.stringify

JSON.stringify(value[, replacer [, space]])
1

# 参数 (opens new window)

  • value

    将要序列化成 一个 JSON 字符串的值。

  • replacer 可选

    1. 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
    2. 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
    3. 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
  • space 可选

    1. 指定缩进用的空白字符串,用于美化输出(pretty-print);
    2. 如果参数是个数字,它代表有多少的空格;上限为10。
    3. 该值若小于1,则意味着没有空格;
    4. 如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;
    5. 如果该参数没有提供(或者为 null),将没有空格。

示范:

// 1. 转换对象
console.log(JSON.stringify({ name: 'samy', sex: 'boy' })) // '{"name":"samy","sex":"boy"}'

// 2. 转换普通值
console.log(JSON.stringify('samy')) // "samy"
console.log(JSON.stringify(1)) // "1"
console.log(JSON.stringify(true)) // "true"
console.log(JSON.stringify(null)) // "null"

// 3. 指定replacer函数
console.log(JSON.stringify({ name: 'samy', sex: 'boy', age: 100 }, (key, value) => {
  return typeof value === 'number' ? undefined : value
}))
// '{"name":"samy","sex":"boy"}'

// 4. 指定数组
console.log(JSON.stringify({ name: 'samy', sex: 'boy', age: 100 }, [ 'name' ]))
// '{"name":"samy"}'

// 5. 指定space(美化输出)
console.log(JSON.stringify({ name: 'samy', sex: 'boy', age: 100 }))
// '{"name":"samy","sex":"boy","age":100}'
console.log(JSON.stringify({ name: 'samy', sex: 'boy', age: 100 }, null , 2))
/*
{
  "name": "samy",
  "sex": "boy",
  "age": 100
}
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 特性

# 特性一
  1. undefined任意的函数以及symbol值,出现在非数组对象的属性值中时在序列化过程中会被忽略
  2. undefined任意的函数以及symbol值出现在数组中时会被转换成 null
  3. undefined任意的函数以及symbol值单独转换时,会返回 undefined
// 1. 对象中存在这三种值会被忽略
console.log(JSON.stringify({
  name: 'samy',
  sex: 'boy',
  // 函数会被忽略
  showName () {
    console.log('samy')
  },
  // undefined会被忽略
  age: undefined,
  // Symbol会被忽略
  symbolName: Symbol('samy')
}))
// '{"name":"samy","sex":"boy"}'

// 2. 数组中存在着三种值会被转化为null
console.log(JSON.stringify([
  'samy',
  'boy',
  // 函数会被转化为null
  function showName () {
    console.log('samy')
  },
  //undefined会被转化为null
  undefined,
  //Symbol会被转化为null
  Symbol('samy')
]))
// '["samy","boy",null,null,null]'

// 3.单独转换会返回undefined
console.log(JSON.stringify(
  function showName () {
    console.log('samy')
  }
)) // undefined
console.log(JSON.stringify(undefined)) // undefined
console.log(JSON.stringify(Symbol('samy'))) // undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 特性二

布尔值数字字符串的包装对象在序列化过程中会自动转换成对应的原始值。

console.log(JSON.stringify([new Number(1), new String("samy"), new Boolean(false)]))
// '[1,"samy",false]'
1
2
# 特性三

所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。

console.log(JSON.stringify({
  [Symbol('samy')]: 'samy'}
)) 
// '{}'
console.log(JSON.stringify({
  [ Symbol('samy') ]: 'samy',
}, (key, value) => {
  if (typeof key === 'symbol') {
    return value
  }
}))
// undefined
1
2
3
4
5
6
7
8
9
10
11
12
# 特性四

NaN 和 Infinity 格式的数值及 null 都会被当做 null。

console.log(JSON.stringify({
  age: NaN,
  age2: Infinity,
  name: null
}))
// '{"age":null,"age2":null,"name":null}'
1
2
3
4
5
6
# 特性五

转换值如果有 toJSON() 方法,该方法定义什么值将被序列化。

const toJSONObj = {
  name: 'samy',
  toJSON () {
    return 'JSON.stringify'
  }
}

console.log(JSON.stringify(toJSONObj))
// "JSON.stringify"
1
2
3
4
5
6
7
8
9
# 特性六

Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。

const d = new Date()

console.log(d.toJSON()) // 2021-10-05T14:01:23.932Z
console.log(JSON.stringify(d)) // "2021-10-05T14:01:23.932Z"
1
2
3
4
# 特性七

对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。

let cyclicObj = {
  name: 'samy',
}

cyclicObj.obj = cyclicObj

console.log(JSON.stringify(cyclicObj))
// Converting circular structure to JSON
1
2
3
4
5
6
7
8
# 特性八[要点]

其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性

let enumerableObj = {}

Object.defineProperties(enumerableObj, {
  name: {
    value: 'samy',
    enumerable: true
  },
  sex: {
    value: 'boy',
    enumerable: false
  },
})

console.log(JSON.stringify(enumerableObj))
// '{"name":"samy"}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 特性九

当尝试去转换 BigInt (opens new window) 类型的值会抛出错误

const alsoHuge = BigInt(9007199254740991)

console.log(JSON.stringify(alsoHuge))
// TypeError: Do not know how to serialize a BigInt
1
2
3
4

# 手写JSON.stringify

终于重新学完JSON.stringify的众多特性啦!咱们根据这些特性来手写一个简单版本的吧(无replacer函数和space

# 源码实现

核心

  • result.push("${key}"😒{jsonstringify(value)});
  • return{${result}}.replace(/'/, '"');
const jsonstringify = (data) => {
  // 确认一个对象是否存在循环引用
  const isCyclic = (obj) => {
  // 使用Set数据类型来存储已经检测过的对象
  let stackSet = new Set()
  let detected = false

  const detect = (obj) => {
    // 不是对象类型的话,可以直接跳过
    if (obj && typeof obj != 'object') {
      return
    }
    // 当要检查的对象已经存在于stackSet中时,表示存在循环引用
    if (stackSet.has(obj)) {
      return detected = true
    }
    // 将当前obj存如stackSet
    stackSet.add(obj)

    for (let key in obj) {
      // 对obj下的属性进行挨个检测
      if (obj.hasOwnProperty(key)) {
        detect(obj[key])
      }
    }
    // 平级检测完成之后,将当前对象删除,防止误判
    /*
      例如:对象的属性指向同一引用,如果不删除的话,会被认为是循环引用
      let tempObj = {
        name: 'samy'
      }
      let obj4 = {
        obj1: tempObj,
        obj2: tempObj
      }
    */
    stackSet.delete(obj)
  }
  detect(obj)
  return detected
}

  // 特性七:
  // 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
  if (isCyclic(data)) {
    throw new TypeError('Converting circular structure to JSON')
  }

  // 特性九:
  // 当尝试去转换 BigInt 类型的值会抛出错误
  if (typeof data === 'bigint') {
    throw new TypeError('Do not know how to serialize a BigInt')
  }

  const type = typeof data
  const commonKeys1 = ['undefined', 'function', 'symbol']
  const getType = (s) => {
    return Object.prototype.toString.call(s).replace(/\[object (.*?)\]/, '$1').toLowerCase()
  }

  // 非对象
  if (type !== 'object' || data === null) {
    let result = data
    // 特性四:
    // NaN 和 Infinity 格式的数值及 null 都会被当做 null。
    if ([NaN, Infinity, null].includes(data)) {
      result = 'null'
      // 特性一:
      // `undefined`、`任意的函数`以及`symbol值`被`单独转换`时,会返回 undefined
    } else if (commonKeys1.includes(type)) {
      // 直接得到undefined,并不是一个字符串'undefined'
      return undefined
    } else if (type === 'string') {
      result = '"' + data + '"'
    }
    return String(result)
  } else if (type === 'object') {
    // 特性五:
    // 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化
    // 特性六:
    // Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
    if (typeof data.toJSON === 'function') {
      return jsonstringify(data.toJSON())
    } else if (Array.isArray(data)) {
      let result = data.map((it) => {
        // 特性一:
        // `undefined`、`任意的函数`以及`symbol值`出现在`数组`中时会被转换成 `null`
        return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
      })

      return `[${result}]`.replace(/'/g, '"')
    } else {
      // 特性二:
      // 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
      if (['boolean', 'number'].includes(getType(data))) {
        return String(data)
      } else if (getType(data) === 'string') {
        return '"' + data + '"'
      } else {
        let result = []
        // 特性八
        // 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
        Object.keys(data).forEach((key) => {
          // 特性三:
          // 所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
          if (typeof key !== 'symbol') {
            const value = data[key]
            // 特性一
            // `undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略
            if (!commonKeys1.includes(typeof value)) {
              result.push(`"${key}":${jsonstringify(value)}`)
            }
          }
        })
        return `{${result}}`.replace(/'/, '"')
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# 测试
// 1. 测试一下基本输出
console.log(jsonstringify(undefined)) // undefined 
console.log(jsonstringify(() => { })) // undefined
console.log(jsonstringify(Symbol('samy'))) // undefined
console.log(jsonstringify((NaN))) // null
console.log(jsonstringify((Infinity))) // null
console.log(jsonstringify((null))) // null
console.log(jsonstringify({
  name: 'samy',
  toJSON() {
    return {
      name: 'samy2',
      sex: 'boy'
    }
  }
}))
// {"name":"samy2","sex":"boy"}

// 2. 和原生的JSON.stringify转换进行比较
console.log(jsonstringify(null) === JSON.stringify(null));
// true
console.log(jsonstringify(undefined) === JSON.stringify(undefined));
// true
console.log(jsonstringify(false) === JSON.stringify(false));
// true
console.log(jsonstringify(NaN) === JSON.stringify(NaN));
// true
console.log(jsonstringify(Infinity) === JSON.stringify(Infinity));
// true
let str = "samy";
console.log(jsonstringify(str) === JSON.stringify(str));
// true
let reg = new RegExp("\w");
console.log(jsonstringify(reg) === JSON.stringify(reg));
// true
let date = new Date();
console.log(jsonstringify(date) === JSON.stringify(date));
// true
let sym = Symbol('samy');
console.log(jsonstringify(sym) === JSON.stringify(sym));
// true
let array = [1, 2, 3];
console.log(jsonstringify(array) === JSON.stringify(array));
// true
let obj = {
  name: 'samy',
  age: 18,
  attr: ['coding', 123],
  date: new Date(),
  uni: Symbol(2),
  sayHi: function () {
    console.log("hello world")
  },
  info: {
    age: 16,
    intro: {
      money: undefined,
      job: null
    }
  },
  pakingObj: {
    boolean: new Boolean(false),
    string: new String('samy'),
    number: new Number(1),
  }
}
console.log(jsonstringify(obj) === JSON.stringify(obj)) 
// true
console.log((jsonstringify(obj)))
// {"name":"samy","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"samy","number":1}}
console.log(JSON.stringify(obj))
// {"name":"samy","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"samy","number":1}}

// 3. 测试可遍历对象
let enumerableObj = {}

Object.defineProperties(enumerableObj, {
  name: {
    value: 'samy',
    enumerable: true
  },
  sex: {
    value: 'boy',
    enumerable: false
  },
})

console.log(jsonstringify(enumerableObj))
// {"name":"samy"}

// 4. 测试循环引用和Bigint
let obj1 = { a: 'aa' }
let obj2 = { name: 'samy', a: obj1, b: obj1 }
obj2.obj = obj2

console.log(jsonstringify(obj2))
// TypeError: Converting circular structure to JSON
console.log(jsonStringify(BigInt(1)))
// TypeError: Do not know how to serialize a BigInt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

# JSON对象与字符串互转

序列化及反序列化

JSON字符串转换为JSON对象:3种方式;

  • var obj = JSON.parse(str);
  • var obj = eval('('+ str +')');
  • //var obj = str.parseJSON();

JSON对象转换为JSON字符串:2种方式;

  • var last=JSON.stringify(obj);
  • //var last=obj.toJSONString();
/**第一种方式*/
console.log("=====第一种方式====");
var temp =  "console.log('string to objects')";
eval(temp);//string to objects

var str = '{"name": "Samy", "age": 20}';
var obj = eval( "("+str + ")" );
console.log(obj);//{ name: 'Samy', age: 20 }

/**第二种方式*/
console.log("=====第二种方式====");
var str2 = '{"name": "Samy", "age": 20}';
var obj2 = JSON.parse(str2);
console.log(obj2);//{ name: 'Samy', age: 20 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

对象转化成字符串(序列化) JSON.stringify的其他方式使用;

var obj3 = { a:1, b:2, c:3 }
console.log("====带参数情况之replacer====");
var jsonStr3 = JSON.stringify(obj3,["c","a","b"]);
console.log(jsonStr3);//{"c":3,"a":1,"b":2}

console.log("====带参数情况之space====");
var jsonStr4 = JSON.stringify(obj3,["c","a","b"],"Samy");
console.log(jsonStr4);
// {
// Samy"c": 3,
// Samy"a": 1,
// Samy"b": 2
// }
console.log("====space【推荐用法】====");
var jsonStr5 = JSON.stringify(obj3,["c","a","b"],"\t");
console.log(jsonStr5);
// {
// 	"c": 3,
// 	"a": 1,
// 	"b": 2
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# json和XML相互转换

下载相关文件

<!--json和XML相互转换-->
<script src="js/jquery-2.1.4.min.js"></script>
<script src="js/jquery.xml2json.js"></script>
<script src="js/jquery.json2xml.js"></script>
<script>
    console.log("=== xml2json ===");
    /**
     * 步骤流程:
     * 1:先转换成JS的对象;
     * 2:再JS对象转换成json
     */
    var str = "<root>" +
            "<name>Samy Zhang</name>" +
            "<age>20</age>" +
            "<friends>Jenny</friends>" +
            "<friends>Li</friends>" +
            "</root>";
    console.log(str);
    var obj = $.xml2json(str);
    console.log(obj);
    var jsonStr = JSON.stringify(obj);
    console.log(jsonStr);
    //{"name":"Samy Zhang","age":"20","friends":["Jenny","Li"]}
    console.log("=== json2xml ===");
    /**
     * 步骤流程:
     * 1:通过json2xml直接转换;
     */
    var person = {
        name: "Samy Zhang",
        age: 20,
        mother: {
            name: "Lucy",
            age: 40
        },
        a: function () {
            return 1;
        },
        b: null,
        c: undefined
    };
    var xmlStr = $.json2xml(person);
    console.log(xmlStr);
// <root>
//   <name>Samy Zhang</name>
//   <age>20</age>
//   <mother>
//     <name>Lucy</name>
//     <age>40</age>
//   </mother>
//   <a>function () {
//             return 1;
//         }</a>
//   <b/>
//   <c>undefined</c>
// </root>
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 案例

json格式化要注意的问题:

undefined任意的函数以及symbol值,出现在非数组对象的属性值中时在序列化过程中会被忽略

比如:

let signInfo = [
  {
    fieldId: 539,
    value: undefined
  },
  {
    fieldId: 540,
    value: undefined
  },
  {
    fieldId: 546,
    value: undefined
  },
]
// 经过JSON.stringify之后的数据,少了value key,导致后端无法读取value值进行报错
// '[{"fieldId":539},{"fieldId":540},{"fieldId":546}]'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 解决方案

问题的原因找到了,解决方式 (这里只讲前端的解决方案,当然也可以由后端解决) 也很简单,将value值为undefined的项转化为空字符串再提交即可。

方案一:新开一个对象处理

let signInfo = [
  {
    fieldId: 539,
    value: undefined
  },
  {
    fieldId: 540,
    value: undefined
  },
  {
    fieldId: 546,
    value: undefined
  },
]
let newSignInfo = signInfo.map((it) => {
  const value = typeof it.value === 'undefined' ? '' : it.value
  return {
    ...it,
    value
  }
})

console.log(JSON.stringify(newSignInfo))
// '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

方案二:利用JSON.stringify第二个参数,直接处理

方案一的缺陷是需要新开一个对象进行一顿操作才能解决,不够优雅

let signInfo = [
  {
    fieldId: 539,
    value: undefined
  },
  {
    fieldId: 540,
    value: undefined
  },
  {
    fieldId: 546,
    value: undefined
  },
]

// 判断到value为undefined,返回空字符串即可
JSON.stringify(signInfo, (key, value) => typeof value === 'undefined' ? '' : value)
// '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Ajax

ajax的全称:Asynchronous Javascript And XML。

异步传输+js+xml。 所谓异步,在这里简单地解释就是:向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验。

# 请求回调Callback方法

  • onSuccess
  • onFailure
  • onUninitialized
  • onLoading
  • onLoaded
  • onInteractive
  • onComplete
  • onException

# 解决浏览器缓存问题

  • 在ajax发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。
  • 在ajax发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。
  • 在URL后面加上一个随机数: "fresh=" + Math.random();。
  • 在URL后面加上时间戳:"nowtime=" + new Date().getTime();。
  • 如果是使用jQuery,直接这样就可以了 $.ajaxSetup({cache:false})。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。

# ajax的优缺点

优点:减轻服务器的负担,按需取数据,最大程度减少冗余请求,局部刷新。 缺点:浏览器之间有差异,对流媒体和移动设备支持不够好

# 创建一个Ajax的流程

  1. 创建XMLHttpRequest对象,也就是创建一个异步调用对象
  2. 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息
  3. 设置响应HTTP请求状态变化的函数
  4. 发送HTTP请求
  5. 获取异步调用返回的数据
  6. 使用JavaScript和DOM实现局部刷新

# 请求封装

ajaxUtil.js

兼容性处理; 事件的触发顺序;如果是POST请求则需要添加头;

var AjaxUtil = {
	// 基础选项
	options: {
		method: "get", // 默认提交的方法,get post
		url: "", // 请求的路径 required
		params: {}, // 请求的参数
		type: "text", // 返回的内容的类型,text,xml,json
		callback: function () { } // 回调函数 required
	},

	// 创建XMLHttpRequest对象
	createRequest: function () {
		var xmlhttp;
		try {
			xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); // IE6以上版本
		} catch (e) {
			try {
				xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // IE6以下版本
			} catch (e) {
				try {
					xmlhttp = new XMLHttpRequest();
					if (xmlhttp.overrideMimeType) {
						xmlhttp.overrideMimeType("text/xml");
					}
				} catch (e) {
					alert("您的浏览器不支持Ajax");
				}
			}
		}
		return xmlhttp;
	},

	// 设置基础选项
	setOptions: function (newOptions) {
		for (var pro in newOptions) {
			this.options[pro] = newOptions[pro];
		}
	},

	// 格式化请求参数
	formateParameters: function () {
		var paramsArray = [];
		var params = this.options.params;
		for (var pro in params) {
			var paramValue = params[pro];
      /*if(this.options.method.toUpperCase() === "GET")
			{
			    paramValue = encodeURIComponent(params[pro]);
			}*/
			paramsArray.push(pro + "=" + paramValue);
		}
		return paramsArray.join("&");
		// method=get&url=&callback=&type=text
	},

	// 状态改变的处理
	readystatechange: function (xmlhttp) {
		var returnValue;// 获取返回值
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			switch (this.options.type) {
				case "xml":
					returnValue = xmlhttp.responseXML;
					break;
				case "json":
					var jsonText = xmlhttp.responseText;
					if (jsonText) {
						returnValue = eval("(" + jsonText + ")");
					}
					break;
				default:
					returnValue = xmlhttp.responseText;
					break;
			}
			if (returnValue) {
				this.options.callback.call(this, returnValue);
			} else {
				this.options.callback.call(this);
			}
		}
	},

	// 发送Ajax请求
	//{'method':'get'}
	request: function (options) {
		var ajaxObj = this;
		// 设置参数
		ajaxObj.setOptions.call(ajaxObj, options);
		// 创建XMLHttpRequest对象
		var xmlhttp = ajaxObj.createRequest.call(ajaxObj);
		// 设置回调函数
		xmlhttp.onreadystatechange = function () {
			ajaxObj.readystatechange.call(ajaxObj, xmlhttp);
		};
		// 格式化参数
		var formateParams = ajaxObj.formateParameters.call(ajaxObj);
		// 请求的方式
		var method = ajaxObj.options.method;
		var url = ajaxObj.options.url;
		if ("GET" === method.toUpperCase()) {
			url += "?" + formateParams;
		}
		// 建立连接
		xmlhttp.open(method, url, true);
		if ("GET" === method.toUpperCase()) {
			xmlhttp.send(null);
		} else if ("POST" === method.toUpperCase()) {// 如果是POST提交,设置请求头信息
			xmlhttp.setRequestHeader(
				"Content-Type",
				"application/x-www-form-urlencoded"
			);
			xmlhttp.send(formateParams);
		}
	}
	//	function $(id) {
	//		return document.getElementById(id);
	//	},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//使用
function findUser() {
  var userid = $("userid").value;
  if(userid) {
    AjaxUtil.request({
      url: "ajax.json",
      params: {
        id: userid
      },
      type: 'json',
      callback: process
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 使用Promise封装AJAX

// promise 封装实现:
function getJSON(url) {
  // 创建一个 promise 对象
  let promise = new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest();
    // 新建一个 http 请求
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;
      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    // 设置错误监听函数
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 请求
    xhr.send(null);
  });
  return promise;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# Ajax、Axios、Fetch有啥区别?

  • Ajax:是对XMLHttpRequest对象(XHR)的封装;
  • Axios:是基于Promise对XHR对象的封装;
  • Fetch:是window的一个方法,也是基于Promise,但是与XHR无关,不支持IE;

# js交互

# BOM 操作

# 简介

BOM(浏览器对象模型)是浏览器本身的一些信息的设置和获取,例如获取浏览器的宽度、高度,设置让浏览器跳转到哪个地址。

  • window.screen对象:包含有关用户屏幕的信息
  • window.location对象:用于获得当前页面的地址(URL),并把浏览器重定向到新的页面
  • window.history对象:浏览历史的前进后退等
  • window.navigator对象:常常用来获取浏览器信息、是否移动端访问等等

BOM就是browser object model浏览器对象模型

api 作用 代表方法或属性
window.history 操纵浏览器的记录 history.back() history.go(-1)
window.innerHeight 获取浏览器窗口的高度
window.innerWidth 获取浏览器窗口的宽度
window.location 操作刷新按钮和地址栏 location.host:获取域名和端口 location.hostname:获取主机名 location.port:获取端口号 location.pathname:获取url的路径 location.search:获取?开始的部分 location.href:获取整个url location.hash:获取#开始的部分 location.origin:获取当前域名 location.navigator:获取当前浏览器信息

# JS中如何将页面重定向到另一个页面?

  • 1、使用 location.href:window.location.href ="www.baidu.com")
  • 2、使用 location.replace: window.location.replace("www.baidu.com")

# 使用

获取屏幕的宽度和高度

console.log(screen.width)
console.log(screen.height)
1
2

获取网址、协议、path、参数、hash 等

// 例如当前网址是 https://baidu.com/timeline/frontend?a=10&b=10#some
console.log(location.href)  // https://baidu.com/timeline/frontend?a=10&b=10#some
console.log(location.protocol) // https:
console.log(location.pathname) // /timeline/frontend
console.log(location.search) // ?a=10&b=10
console.log(location.hash) // #some
1
2
3
4
5
6

另外,还有调用浏览器的前进、后退功能等

history.back()
history.forward()
1
2

获取浏览器特性(即俗称的UA)然后识别客户端,例如判断是不是 Chrome 浏览器

var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)
1
2
3

# DOM操作

添加、移除、移动、复制、创建和查找节点

# DOM与BOM的区别

  • 文档对象类型(DOM):把整个页面规划成由节点层级构成的文档
  • 浏览器对象模型(BOM):处理浏览器宽口和框架

BOM全称Browser Object Model,即浏览器对象模型,主要处理浏览器窗口和框架。

DOM全称Document Object Model,即文档对象模型,是 HTML 和XML 的应用程序接口(API),遵循W3C 的标准,所有浏览器公共遵守的标准。

JS是通过访问BOM(Browser Object Model)对象来访问、控制、修改客户端(浏览器),由于BOM的window包含了document,window对象的属性和方法是直接可以使用而且被感知的,因此可以直接使用window对象的document属性,通过document属性就可以访问、检索、修改XHTML文档内容与结构。因为document对象又是DOM的根节点。

可以说,BOM包含了DOM(对象),浏览器提供出来给予访问的是BOM对象,从BOM对象再访问到DOM对象,从而js可以操作浏览器以及浏览器读取到的文档。

# DOM 树

17-35-02

# 文档碎片

一个容器,用于暂时存放创建的dom元素,使用document.createDocumentFragment()创建;

# 作用

将需要添加的大量元素 先添加到文档碎片 中,再将文档碎片添加到需要插入的位置,大大减少dom操作,提高性能

# 案例
var oFragmeng = document.createDocumentFragment(); 
for(var i=0;i<10000;i++){ 
    var op = document.createElement("span"); 
    var oText = document.createTextNode(i); 
    op.appendChild(oText); 
    //先附加在文档碎片中
    oFragmeng.appendChild(op);  
} 
document.body.appendChild(oFragmeng); //最后一次性添加到document中
1
2
3
4
5
6
7
8
9

# DOM的类型

12种

  1. 元素节点   Node.ELEMENT_NODE(1)
  2. 属性节点   Node.ATTRIBUTE_NODE(2)
  3. 文本节点   Node.TEXT_NODE(3)
  4. CDATA节点 Node.CDATA_SECTION_NODE(4)
  5. 实体引用名称节点    Node.ENTRY_REFERENCE_NODE(5)
  6. 实体名称节点   Node.ENTITY_NODE(6)
  7. 处理指令节点   Node.PROCESSING_INSTRUCTION_NODE(7)
  8. 注释节点   Node.COMMENT_NODE(8)
  9. 文档节点   Node.DOCUMENT_NODE(9)
  10. 文档类型节点   Node.DOCUMENT_TYPE_NODE(10)
  11. 文档片段节点   Node.DOCUMENT_FRAGMENT_NODE(11)
  12. DTD声明节点 Node.NOTATION_NODE(12)

# 常用操作

总括:

标题 描述
createElement 创建一个标签节点
createTextNode 创建一个文本节点
cloneNode(deep) 复制一个节点,连同属性与值都复制,deep为true时,连同后代节点一起复制,不传或者传false,则只复制当前节点
createDocumentFragment 创建一个文档碎片节点
appendChild 追加子元素
insertBefore 将元素插入前面
removeChild 删除子元素
replaceChild 替换子元素
getAttribute 获取节点的属性
createAttribute 创建属性
setAttribute 设置节点属性
romoveAttribute 删除节点属性
element.attributes 将属性生成类数组对象
# 创建新节点
  • createDocumentFragment() //创建一个DOM片段
  • createElement() //创建一个具体的元素
  • createTextNode() //创建一个文本节点
# 添加、移除、替换、插入
  • appendChild()
  • removeChild()
  • replaceChild()
  • insertBefore() //在已有的子节点前插入一个新的子节点
# 查找
  • getElementsByTagName() //通过标签名称
  • getElementsByName() //通过元素的Name属性的值(IE容错能力较强,会得到一个数组,其中包括id等于name值的)
  • getElementById() //通过元素Id,唯一性

使用

<ul> 
    <li> index = 0 </li> 
    <li> index = 1 </li> 
    <li> index = 2 </li> 
    <li> index = 3 </li>
</ul>
<script type="text/javascript"> 
    var nodes = document.getElementsByTagName('li'); 
    for(i = 0;i<nodes.length;i+=1) { 
        nodes[i].onclick = function() { 
            console.log(i+1); //不使用闭包的话,值每次都是4 
        }(4);
     }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 结合 jquery的使用
  • 获取设定内容:text(), html() and val()

  • 获取设定 CSS Class:addClass(), removeClass(), hasClass(), toggleClass

  • 获取设定 CSS style :css()

     $('.photo-cover').css('background-image', 'url("data:image/jpg;base64,' + picBase64 + '")');
    
    1
  • 获取设定属性:attr(), removeAttr()

  • 获取自定义属性:this.getAttribute()

    res.pd.forEach(function(item, idx, arr) {
        storeList.append('<div class="list-item" data-id="' + item.ID + '">' + item.name + '</div>');
    })
    shopId = this.getAttribute('data-id');
    
    1
    2
    3
    4

# 在JS中访问HTML元素的不同方式

  • getElementById(‘idname’): 按id名称获取元素
  • getElementsByClass(‘classname’): 获取具有给定类名的所有元素
  • getElementsByTagName(‘tagname’): 获取具有给定标记名称的所有元素
  • querySelector(): 此函数采用css样式选择器并返回第一个选定元素

# 获取DOM元素有哪些方法

方法 描述 返回类型
document.getElementById(id) 通过id获取dom 符合条件的dom对象
document.getElementsByTagName(tagName) 通过标签名获取dom 符合条件的所有dom对象组成的类数组
document.getElementsByClassName(class) 通过class获取dom 符合条件的所有dom对象组成的类数组
document.getElementsByName(name) 通过标签的属性name获取dom 符合条件的所有dom对象组成的类数组
document.querySelector(选择器) 通过选择器获取dom 符合条件的第一个dom对象
document.querySelectorAll(选择器) 通过选择器获取dom 符合条件的所有dom对象组成的类数组

# 如何统计网⻚上出现了多少种标签

  1. 获取所有的DOM节点;document.querySelectorAll('*')
  2. NodeList集合转化为数组;[...document.querySelectorAll('*')]
  3. 获取数组每个元素的标签名;[...document.querySelectorAll('*')}.map(ele => ele.tagName)
  4. 去重; new Set([...document.querySelectorAll('*').map(ele=>ele.tagName)).size

# 专用名词区别

# window 与 document的区别

window对象是指浏览器打开的窗口。 document对象是Documentd对象(HTML 文档对象)的一个只读引用,window对象的一个属性。

  • window:JS 的 window 是一个全局对象,它包含变量、函数、historylocation

  • document:document也位于window之下,可以视为window的属性

# innerHTML 和 innerText的区别
  • innerHTML: 也就是从对象的起始位置到终止位置的全部内容,包括Html标签

  • innerText: 从起始位置到终止位置的内容, 但它去除Html标签

<div id=”app”></div>
const greeting = “Hello there!”;
const appDiv = document.getElementById(“app”);
appDiv.innerText = greeting;

<div id=”app”>{{ greeting }}</div>
new Vue({
    data: {
       greeting: ‘Hello There!’
    },
   el: ‘#app’
});
1
2
3
4
5
6
7
8
9
10
11
12
# documen.write和 innerHTML的区别
  • document.write:只能重绘整个页面
  • innerHTML:可以重绘页面的一部分

# js的各种位置

  • clientHeight:表示的是可视区域的高度,不包含border和滚动条
  • offsetHeight:表示可视区域的高度,包含了border和滚动条
  • scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。
  • clientTop:表示边框border的厚度,在未指定的情况下一般为0
  • scrollTop:滚动后被隐藏的高度,获取对象相对于由offsetParent属性指定的父坐标(css定位的元素或body元素)距离顶端的高度。

# 各种高度的区别

# 图示

# height、clientHeight、scrollHeight、offsetHeight区别

关系公式: element.clientHeight + element.scrollTop === element.scrollHeight

解释:此公式可以用于判断是否滚动到底;

# 鼠标事件的各坐标

属性 说明 兼容性
offsetX 以当前的目标元素左上角为原点,定位x轴坐标 除Mozilla外都兼容
offsetY 以当前的目标元素左上角为原点,定位y轴坐标 除Mozilla外都兼容
clientX 以浏览器可视窗口左上角为原点,定位x轴坐标 都兼容
clientY 以浏览器可视窗口左上角为原点,定位y轴坐标 都兼容
pageX 以doument对象左上角为原点,定位x轴坐标 除IE外都兼容
pageY 以doument对象左上角为原点,定位y轴坐标 除IE外都兼容
screenX 以计算机屏幕左上顶角为原点,定位x轴坐标(多屏幕会影响) 全兼容
screenY 以计算机屏幕左上顶角为原点,定位y轴坐标 全兼容
layerX 最近的绝对定位的父元素(如果没有,则为 document 对象)左上顶角为元素,定位 x 轴坐标 Mozilla 和 Safari
layerY 最近的绝对定位的父元素(如果没有,则为 document 对象)左上顶角为元素,定位 y 轴坐标 Mozilla 和 Safari

# 元素视图

属性 说明
offsetLeft 获取当前元素到定位父节点的left方向的距离
offsetTop 获取当前元素到定位父节点的top方向的距离
offsetWidth 获取当前元素 width + 左右padding + 左右border-width
offsetHeight 获取当前元素 height + 上下padding + 上下border-width
clientWidth 获取当前元素 width + 左右padding
clientHeight 获取当前元素 height + 上下padding
scrollWidth 当前元素内容真实的宽度,内容不超出盒子宽度时为盒子的clientWidth
scrollHeight 当前元素内容真实的高度,内容不超出盒子高度时为盒子的clientHeight

# Window视图

属性 说明
innerWidth innerWidth 浏览器窗口可视区宽度(不包括浏览器控制台、菜单栏、工具栏)
innerHeight innerWidth 浏览器窗口可视区高度(不包括浏览器控制台、菜单栏、工具栏)

# Document文档视图

属性 说明
document.documentElement.clientWidth 浏览器窗口可视区宽度(不包括浏览器控制台、菜单栏、工具栏、滚动条)
document.documentElement.clientHeight 浏览器窗口可视区高度(不包括浏览器控制台、菜单栏、工具栏、滚动条)
document.documentElement.offsetHeight 获取整个文档的高度(包含body的margin)
document.body.offsetHeight 获取整个文档的高度(不包含body的margin)
document.documentElement.scrollTop 返回文档的滚动top方向的距离(当窗口发生滚动时值改变)
document.documentElement.scrollLeft 返回文档的滚动left方向的距离(当窗口发生滚动时值改变)

# 获取盒子宽高几种方式及区别

  • dom.style.width/height :这种方式只能取到dom元素内联样式所设置的宽高,也就是说如果该节点的样式是在style标签中或外联的CSS文件中设置的话,通过这种方法是获取不到dom的宽高的

  • dom.currentStyle.width/height :获取渲染后的宽高。但是仅IE支持

  • window.getComputedStyle(dom).width/height与2原理相似,但是兼容性,通用性更好一些

  • dom.getBoundingClientRect().width/height:计算元素绝对位置,获取到四个元素left,top,width,height

获取浏览器高度和宽度的兼容性写法:

var w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
var h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
1
2
const {clientHeight, clientWidth} = document.getElementsByClassName(styles.autoSize)[0];

@debounce(300)
resize() {
    const height = document.getElementsByClassName(styles.partition)[0].offsetHeight;
    this.setState({ height: height - 49 });
}
1
2
3
4
5
6
7

# 几个核心视图API

# getBoundingClientRect

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。返回的是一个对象,对象里有这8个属性:left,right,top,bottom,width,height,x,y;

image-20211110105748247

判断元素是否在可视区域

这是getBoundingClientRect最常应用的场景了,判断一个元素是否完整出现在视口里;

根据这个用处,咱们可以实现:懒加载和无限滚动

// html
<div id="box"></div>
body {
  height: 3000px;
  width: 3000px;
}
#box {
width: 300px;
height: 300px;
background-color: red;
margin-top: 300px;
margin-left: 300px;
}
// js
const box = document.getElementById('box')
window.onscroll = function () {
  // box完整出现在视口里才会输出true,否则为false
  console.log(checkInView(box))
}
function checkInView(dom) {
  const { top, left, bottom, right } = dom.getBoundingClientRect()
  console.log(top, left, bottom, right)
  console.log(window.innerHeight, window.innerWidth)
  return top >= 0 &&
    left >= 0 &&
    bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    right <= (window.innerWidth || document.documentElement.clientWidth)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

缺点

  • 1、每次scroll都得重新计算,性能耗费大
  • 2、引起重绘回流

# IntersectionObserver

IntersectionObserver接口 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport (opens new window))交叉状态的方法。祖先元素与视窗(viewport (opens new window))被称为根(root)

通俗点说就是:IntersectionObserver是用来监听某个元素与视口交叉状态的。交叉状态是什么呢?请看下图,一开始整个元素都在视口内,那么元素与视口的交叉状态就是100%,而我往下滚动,元素只有一半显示在视口里,那么元素与视口的交叉状态为50%

image-20211110110333176

用法

// 接收两个参数 callback  option
var io = new IntersectionObserver(callback, option);
// 开始观察(可观察多个元素)
io.observe(document.getElementById('example1'));
io.observe(document.getElementById('example2'));
// 停止观察某个元素
io.unobserve(element);
// 关闭观察器
io.disconnect();
1
2
3
4
5
6
7
8
9

callback

callback一般有两种触发情况。一种是目标元素刚刚进入视口(可见),另一种是完全离开视口(不可见)。

var io = new IntersectionObserver(
  entries => {
    console.log(entries);
  }
);
1
2
3
4
5

callback函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry (opens new window)对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。

image-20211110110541779

# IntersectionObserverEntry对象
{
  time: 3893.92,
  rootBounds: ClientRect {
    bottom: 920,
    height: 1024,
    left: 0,
    right: 1024,
    top: 0,
    width: 920
  },
  boundingClientRect: ClientRect {
     // ...
  },
  intersectionRect: ClientRect {
    // ...
  },
  intersectionRatio: 0.54,
  target: element
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

属性解析:

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  • target:被观察的目标元素,是一个 DOM 节点对象
  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
  • boundingClientRect:目标元素的矩形区域的信息
  • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
  • intersectionRatio:目标元素的可见比例,即intersectionRectboundingClientRect的比例,完全可见时为1,完全不可见时小于等于0

option

讲讲第二个参数option里比较重要的两个属性:threshold和root

首先讲讲threshold

threshold属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。

new IntersectionObserver(
  entries => {/* ... */}, 
  {
    threshold: [0, 0.25, 0.5, 0.75, 1]
  }
);
1
2
3
4
5
6

用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。

再说说root

IntersectionObserver API 支持容器内滚动。root属性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点。

new IntersectionObserver(
  entries => {/* ... */}, 
  {
    threshold: [0, 0.25, 0.5, 0.75, 1],
    root: document.getElementById('#container')
  }
);
1
2
3
4
5
6
7

完整例子

body {
  height: 3000px;
  width: 3000px;
}
#box1 {
width: 300px;
height: 300px;
background-color: red;
margin-top: 100px;
margin-left: 300px;
}
#box2 {
width: 300px;
height: 300px;
background-color: red;
margin-top: 100px;
margin-left: 300px;
}
<div id="box1"></div>
<div id="box2"></div>

const io = new IntersectionObserver(entries => {
  console.log(entries)
}, {
  threshold: [0, 0.25, 0.5, 0.75, 1]
  // root: xxxxxxxxx
})
io.observe(document.getElementById('box1'))
io.observe(document.getElementById('box2'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

使用场景

  • 1、可以像getBoundingClientRect那样判断元素是否在视口里,并且好处是,不会引起重绘回流
  • 2、同理,有了第一点功能,就可以做懒加载和无限滚动功能了

缺点

想兼容IE的就别考虑这个API了;

# MutationObserver

MutationObserver 是一个内建对象,它观察 DOM 元素,并在检测到更改时触发回调。

用法

// 选择需要观察变动的节点
const targetNode = document.getElementById('some-id');
// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };

// 当观察到变动时执行的回调函数
const callback = function(mutationsList, observer) {
    // Use traditional 'for loops' for IE 11
    for(let mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type === 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
// 之后,可停止观察
observer.disconnect();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

config

config 是一个具有布尔选项的对象,该布尔选项表示“将对哪些更改做出反应”:

  • childList —— node 的直接子节点的更改,
  • subtree —— node 的所有后代的更改,
  • attributes —— node 的特性(attribute),
  • attributeFilter —— 特性名称数组,只观察选定的特性。
  • characterData —— 是否观察 node.data(文本内容)

其他几个选项:

  • attributeOldValue —— 如果为 true,则将特性的旧值和新值都传递给回调(参见下文),否则只传新值(需要 attributes 选项),
  • characterDataOldValue —— 如果为 true,则将 node.data 的旧值和新值都传递给回调(参见下文),否则只传新值(需要 characterData 选项)。

# getComputedStyle

Window.getComputedStyle()方法返回一个对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有CSS属性的值。 私有的CSS属性值可以通过对象提供的API或通过简单地使用CSS属性名称进行索引来访问。

window.getComputedStyle(element, pseudoElement)
1
  • element: 必需,要获取样式的元素。
  • pseudoElement: 可选,伪类元素,当不查询伪类元素的时候可以忽略或者传入 null。

image-20211110112244769

使用

搭配getPropertyValue可以获取到具体样式

// html
#box {
width: 300px;
height: 300px;
background-color: yellow;
}

<div id="box"></div>

const box = document.getElementById('box')
const styles = window.getComputedStyle(box)
// 搭配getPropertyValue可以获取到具体样式
const height = styles.getPropertyValue("height")
const width = styles.getPropertyValue("width")
console.log(height, width) // ’300px‘ '300px'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# createNodeIterator

# 说一说,如何遍历输出页面中的所有元素

那如何使用createNodeIterator对页面中所有元素进行遍历输出呢?

const body = document.getElementsByTagName('body')[0]
    const it = document.createNodeIterator(body)
    let root = it.nextNode()
    while(root) {
        console.log(root)
        root = it.nextNode()
    }
1
2
3
4
5
6
7

找个网站测试下:详细参数可以看这里 (opens new window),讲的很详细

image-20211110111823446

# requestAnimationFrame【rAF 要点】

传统的 javascript 动画是通过定时器 setTimeout 或者 setInterval 实现的。但是定时器动画一直存在两个问题,

  • 第一个就是动画的循时间环间隔不好确定,设置长了动画显得不够平滑流畅,设置短了浏览器的重绘频率会达到瓶颈,推荐的最佳循环间隔是17ms(大多数电脑的显示器刷新频率是60Hz,1000ms/60);
  • 第二个问题是定时器第二个时间参数只是指定了多久后将动画任务添加到浏览器的UI线程队列中,如果UI线程处于忙碌状态,那么动画不会立刻执行。

为了解决这些问题,HTML5 中加入了 requestAnimationFrame;

# 优点
  1. requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率;
  2. 在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的 CPU、GPU 和内存使用量
  3. requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了 CPU 开销
# 场景
# js动画

requestAnimationFrame 本来就是为动画而生的,所以在处理 js 动画不在话下,与定时器的用法非常相似,下面是一个例子,点击元素时开始转动,再次点击转动速速增加。

var deg = 0;
var id;
var div = document.getElementById("div");
div.addEventListener('click', function () {
    var self = this;
    requestAnimationFrame(function change() {
        self.style.transform = 'rotate(' + (deg++) + 'deg)';
        id = requestAnimationFrame(change);
    });
});
document.getElementById('stop').onclick = function () {
    cancelAnimationFrame(id);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
# 大数据渲染

在大数据渲染过程中,比如表格的渲染,如果不进行一些性能策略处理,就会出现 UI 冻结现象,用户体验极差。有个场景,将后台返回的十万条记录插入到表格中,如果一次性在循环中生成 DOM 元素,会导致页面卡顿5s左右。这时候我们就可以用 requestAnimationFrame 进行分步渲染,确定最好的时间间隔,使得页面加载过程中很流畅。

var total = 100000;
var size = 100;
var count = total / size;
var done = 0;
var ul = document.getElementById('list');

function addItems() {
    var li = null;
    var fg = document.createDocumentFragment();

    for (var i = 0; i < size; i++) {
        li = document.createElement('li');
        li.innerText = 'item ' + (done * size + i);
        fg.appendChild(li);
    }

    ul.appendChild(fg);
    done++;

    if (done < count) {
        requestAnimationFrame(addItems);
    }
};

requestAnimationFrame(addItems);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 兼容性

firefox、chrome、ie10以上, requestAnimationFrame 的支持很好,但不兼容 IE9及以下浏览器,但是我们可以用定时器来做一下兼容,以下是兼容代码:

(function () {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame =
            window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function (callback) {
            /*调整时间,让一次动画等待和执行时间在最佳循环时间间隔内完成*/
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function () {
                    callback(currTime + timeToCall);
                },
                timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function (id) {
            clearTimeout(id);
        };
}());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 性能对比

以上面大数据渲染为例,我们向一个页面中插入1万条数据。 下面是用 setTimeout 后浏览器帧率:

img

下面是用 requestAnimationFrame 后浏览器帧率:

img

我们会发现,性能提升的还是很多的。所以还是很推荐使用 requestAnimationFrame;

# requestIdleCallback【要点】

# 引入

我们都知道React 16实现了新的调度策略(Fiber), 新的调度策略提到的异步、可中断,其实就是基于浏览器的 requestIdleCallback和requestAnimationFrame两个API。虽然React是自己实现了一套类似的requestIdleCallback机制,不过大同小异,还是有必要了解一下这两个API。

# 简介

当关注用户体验,不希望因为一些不重要的任务(如统计上报)导致用户感觉到卡顿的话,就应该考虑使用requestIdleCallback。因为requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态。

requestIdleCallback will schedule work when there is free time at the end of a frame, or when the user is inactive.

requestIdleCallback用法示例

requestIdelCallback(myNonEssentialWork);
function myNonEssentialWork (deadline) {
  // deadline.timeRemaining()可以获取到当前帧剩余时间
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    doWorkIfNeeded();
  }
  if (tasks.length > 0){
    requestIdleCallback(myNonEssentialWork);
  }
}
1
2
3
4
5
6
7
8
9
10
# requestIdleCallback和requestAnimationFrame区别

requestAnimationFrame的回调会在每一帧确定执行,属于高优先级任务,而requestIdleCallback的回调则不一定,属于低优先级任务。 我们所看到的网页,都是浏览器一帧一帧绘制出来的,通常认为FPS为60的时候是比较流畅的,而FPS为个位数的时候就属于用户可以感知到的卡顿了,那么在一帧里面浏览器都要做哪些事情呢,如下所示:

image-20211110191932129

图中一帧包含了用户的交互、js的执行、以及requestAnimationFrame的调用,布局计算以及页面的重绘等工作。 假如某一帧里面要执行的任务不多,在不到16ms(1000/60)的时间内就完成了上述任务的话,那么这一帧就会有一定的空闲时间,这段时间就恰好可以用来执行requestIdleCallback的回调,如下图所示:

image-20211110191953401

当程序栈为空页面无需更新的时候,浏览器其实处于空闲状态,这时候留给requestIdleCallback执行的时间就可以适当拉长,最长可达到50ms,以防出现不可预测的任务(用户输入)来临时无法及时响应可能会引起用户感知到的延迟。

image-20211110192019461

由于requestIdleCallback利用的是帧的空闲时间,所以就有可能出现浏览器一直处于繁忙状态,导致回调一直无法执行,这其实也并不是我们期望的结果(如上报丢失),那么这种情况我们就需要在调用requestIdleCallback的时候传入第二个配置参数timeout了

requestIdleCallback(myNonEssentialWork, { timeout: 2000 });
function myNonEssentialWork (deadline) {
  // 当回调函数是由于超时才得以执行的话,deadline.didTimeout为true
  while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
         tasks.length > 0) {
       doWorkIfNeeded();
    }
  if (tasks.length > 0) {
    requestIdleCallback(myNonEssentialWork);
  }
}
1
2
3
4
5
6
7
8
9
10
11

如果是因为timeout回调才得以执行的话,其实用户就有可能会感觉到卡顿了,因为一帧的执行时间必然已经超过16ms了

# 里面可以执行DOM修改操作?

强烈建议不要,从上面一帧的构成里面可以看到,requestIdleCallback回调的执行说明前面的工作(包括样式变更以及布局计算)都已完成。如果我们在callback里面做DOM修改的话,之前所做的布局计算都会失效,而且如果下一帧里有获取布局(如getBoundingClientRect、clientWidth)等操作的话,浏览器就不得不执行强制重排工作,这会极大的影响性能,另外由于修改dom操作的时间是不可预测的,因此很容易超出当前帧空闲时间的阈值,故而不推荐这么做。推荐的做法是在requestAnimationFrame里面做dom的修改,可以在requestIdleCallback里面构建Document Fragment,然后在下一帧的requestAnimationFrame里面应用Fragment。

除了不推荐DOM修改操作外,Promise的resolve(reject)操作也不建议放在里面,因为Promise的回调会在idle的回调执行完成后立刻执行,会拉长当前帧的耗时,所以不推荐。

推荐放在requestIdleCallback里面的应该是小块的(microTask)并且可预测时间的任务。

# 兼容情况

推荐使用npm包request-idle-callback (opens new window)

image-20211110192109226

# DOMContentLoaded

当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载。

这时问题又来了,“HTML 文档被加载和解析完成”是什么意思呢?或者说,HTML 文档被加载和解析完成之前,浏览器做了哪些事情呢?那我们需要从浏览器渲染原理来谈谈。

浏览器向服务器请求到了 HTML 文档后便开始解析,产物是 DOM(文档对象模型),到这里 HTML 文档就被加载和解析完成了。如果有 CSS 的会根据 CSS 生成 CSSOM(CSS 对象模型),然后再由 DOM 和 CSSOM 合并产生渲染树。有了渲染树,知道了所有节点的样式,下面便根据这些节点以及样式计算它们在浏览器中确切的大小和位置,这就是布局阶段。有了以上这些信息,下面就把节点绘制到浏览器上。所有的过程如下图所示:

image-20211110193434966

# 异步脚本

我们到这里一直在说同步脚本对网页渲染的影响,如果我们想让页面尽快显示,那我们可以使用异步脚本。HTML5 中定义了两个定义异步脚本的方法:defer 和 async。我们来看一看他们的区别。

defer 与 DOMContentLoaded

如果 script 标签中包含 defer,那么这一块脚本将不会影响 HTML 文档的解析,而是等到 HTML 解析完成后才会执行。而 DOMContentLoaded 只有在 defer 脚本执行结束后才会被触发。 所以这意味着什么呢?HTML 文档解析不受影响,等 DOM 构建完成之后 defer 脚本执行,但脚本执行之前需要等待 CSSOM 构建完成。在 DOM、CSSOM 构建完毕,defer 脚本执行完成之后,DOMContentLoaded 事件触发。

async 与 DOMContentLoaded

如果 script 标签中包含 async,则 HTML 文档构建不受影响,解析完毕后,DOMContentLoaded 触发,而不需要等待 async 脚本执行、样式表加载等等。

# DOMContentLoaded 与 load

另外需要提一下的是,我们在 jQuery 中经常使用的 (document).ready(function()//...代码...);其实监听的就是DOMContentLoaded事件,而(document).ready(function() { // ...代码... }); 其实监听的就是 DOMContentLoaded 事件,而 (document).ready(function()//...代码...);其实监听的就是DOMContentLoaded事件,而(document).load(function() { // ...代码... }); 监听的是 load 事件。

document.addEventListener("DOMContentLoaded", function(event) {
      console.log("DOM fully loaded and parsed");
  });

1
2
3
4

# JS事件

# 鼠标事件

注明:鼠标左中右键看event对象上的button属性,对应1、2、3

事件 说明
click 单机鼠标左键触发,右键无效,当用户焦点在按钮并按下Enter,也会触发
dbclick 双击鼠标左键触发,右键无效
mousedown 单机鼠标任意一个按键都触发
mouseout 鼠标指针位于某个元素上且将要移出元素边界时触发
mouseover 鼠标指针移出某个元素到另一个元素上时触发
mouseup 鼠标指针移出某个元素到另一个元素上时触发
mouseover 松开任意鼠标按键时触发
mousemove 鼠标在某个元素上时持续发生
mouseenter 鼠标进入某个元素边界时触发
mouseleave 鼠标离开某个元素边界时触发

# 键盘事件

注明:event对象上的keyCode属性,是按下的按键的ASCLL值,通过这个值可辨别是按下哪个按键。ASCLL表在此ASCII码一览表,ASCII码对照表 (opens new window)

事件 说明
onkeydown 某个键盘按键被按下时触发
onkeyup 某个键盘按键被松开时触发
onkeypress 某个按键被按下时触发,不监听功能键,如ctrl,shift

# 事件模型

DOM2.0 模型将事件处理流程分为三个阶段,即 事件捕获阶段事件处理阶段事件冒泡阶段

  • 事件捕获:当用户触发点击事件后,顶层对象 document 就会发出一个事件流,从最外层的 DOM 节点向目标元素节点传递,最终到达目标元素。
  • 事件处理:当到达目标元素之后,执行目标元素绑定的处理函数。如果没有绑定监听函数,则不做任何处理。
  • 事件冒泡:事件流从目标元素开始,向最外层DOM节点传递,途中如果有节点绑定了事件处理函数,这些函数就会被执行。

总括:

  • 事件冒泡:由最具体的元素接收,并往上传播
  • 事件捕获:由最不具体的元素接收,并往下传播
  • DOM事件流:事件捕获 -> 目标阶段 -> 事件冒泡

详细部分:可以参照【jquery中的详解】;

  • 捕获型事件(Event Capturing) DOM标准同时支持两种事件模型,即捕获型事件与冒泡型事件(默认)
  • 冒泡型事件(Event Bubbling) IE浏览器
element.addEventListener(event, function, useCapture)
//useCapture	可选。布尔值,指定事件是否在捕获或冒泡阶段执行。
//true - 事件句柄在捕获阶段执行
//false- false- 默认。事件句柄在冒泡阶段执行

//可以使用removeEventListener() 方法来移除 addEventListener() 方法添加的事件句柄。
document.getElementById("myDIV").removeEventListener("mousemove", myFunction)
1
2
3
4
5
6
7

# 绑定点击事件三种方式

  • xxx.onclick = function (){}
  • <xxx onclick=""></xxx>
  • xxx.addEventListence('click', function(){}, false)

# 事件流模型

在DOM标准事件模型中,是先捕获后冒泡IE只支持事件冒泡。添加事件方式时,默认是不捕获只做冒泡出来;

冒泡事件是从内向外;捕获是从外向内

捕获:window->document->html->body->......->目标元素

冒泡:相反;

17-41-22

# 如果要让事件先冒泡后捕获,怎么方案

在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获之间

# 冒泡

**事件冒泡是HTML DOM API中事件传播的一种方式,当一个事件发生在另一个元素中的一个元素中,并且两个元素都注册了该事件的句柄时。**通过冒泡,事件首先由最内部的元素捕获和处理,然后传播到外部元素。执行从该事件开始,并转到其父元素。然后执行传递给父元素,以此类推,直到body元素。

# 事件是?IE与火狐的事件机制有什么区别? 如何阻止冒泡?

  1. 我们在网页中的某个操作(有的操作对应多个事件)。例如:当我们点击一个按钮就会产生一个事件。是可以被 JavaScript 侦测到的行为。
  2. 事件处理机制:IE是事件冒泡; Firefox同时支持两种事件模型,也就是:捕获型事件和冒泡型事件
  3. ev.stopPropagation();(旧ie的方法 ev.cancelBubble = true;)
# 阻止冒泡的方法
  • 阻止事件传播(冒泡): e.stopPropagation(), (旧ie的方法 ev.cancelBubble = true;)
  • 阻止默认行为: e.preventDefault()
function stopBubble(e){
    //IE用cancelBubble=true来阻止而FF下需要用stopPropagation方法
    var evt = e || window.event; 
    evt.stopPropagation? evt.stopPropagation() : (evt.cancelBubble=true);
}
$xxx.click(function(e) {
    e.stopPropagation();
    e.cancelBubble = true;// ie
})

//简化写法
function stopBubble(e) {
  if (e.stopPropagation) {
    e.stopPropagation()
  } else {
    window.event.cancelBubble = true;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

阻止事件默认行为

function stopDefault(e) {
  if (e.preventDefault) {
    e.preventDefault();
  } else {
    window.event.returnValue = false;
  }
}
1
2
3
4
5
6
7
# 获取光标的水平位置
function getX(e){
    e = e || window.event; //先检查非IE浏览器,在检查IE的位置
    return e.pageX || e.clentX + document.body.scrollLeft;
}
1
2
3
4

捕获及冒泡示例

<style type="text/css">
  #one {
    width: 800px;
    height: 800px;
    background: blue;
  }
  #two {
    width: 600px;
    height: 600px;
    background: white;
  }
  #three {
    width: 400px;
    height: 400px;
    background: green;
  }
  #four {
    width: 200px;
    height: 200px;
    background: yellow;
  }
</style>

<div id='one'> one
  <div id='two'> two
    <div id='three'> three
      <div id='four'> four
      </div>
    </div>
  </div>
</div>
<script type='text/javascript'>
  var one = document.getElementById('one');
  var two = document.getElementById('two');
  var three = document.getElementById('three');
  var four = document.getElementById('four');
  // one.addEventListener('click', function () {
  //   alert('one');
  // }, false);
  // two.addEventListener('click', function () {
  //   alert('two');
  // }, false);
  // three.addEventListener('click', function () {
  //   alert('three');
  // }, false);
  // four.addEventListener('click', function () {
  //   alert('four');
  // }, false); //都是冒泡:直接有子到父
  
  two.addEventListener('click', function () {
    alert('two 冒泡');
  }, false);
  two.addEventListener('click', function () {
    alert('two 捕获');
  }, true);
  three.addEventListener('click', function () {
    alert('three 冒泡');
  }, false);
  three.addEventListener('click', function () {
    alert('three 捕获');
  }, true);
  four.addEventListener('click', function () {
    alert('four 捕获');
  }, true);
  four.addEventListener('click', function () {
    alert('four 冒泡');
  }, false);
  //顺序为V型;先父到子捕获,再子到父冒泡;
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# 事件移除

事件移除的优化注意事项:相同事件绑定和解除,需要使用共用函数;绑定和解除事件时 事件没有"on" 即onclick写成click; 共用函数不能带参数;

document.body.addEventListener('touchmove', function (event) {
    event.preventDefault();
},false);
document.body.removeEventListener('touchmove', function (event) {
    event.preventDefault();
},false);
//以上为错误写法;下面为正确写法;
function bodyScroll(event){
    event.preventDefault();
}
document.body.addEventListener('touchmove',bodyScroll,false);
document.body.removeEventListener('touchmove',bodyScroll,false);
1
2
3
4
5
6
7
8
9
10
11
12

简单示例:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试事件冒泡</title>
    <style>
        div{padding:40px;}
        #div1{background: #00B83F;}
        #div2{background: #2a6496}
        #div3{background: #93C3CF}
    </style>
    </head>
<body>
  <div id="div1">
      <div id="div2">
          <div id="div3"></div>
      </div>
  </div>
</body>
</html> 
<script>
    window.onload=function (){
        var odiv1=document.getElementById("div1");
        var odiv2=document.getElementById("div2");
        var odiv3=document.getElementById("div3");

        function fdiv1(){
            alert("div1");
        }
        function fdiv2(){
            alert("div2");
        }
        function fdiv3(ev){ 
            alert("div3");
        }
        odiv1.onclick=fdiv1;
        odiv2.onclick=fdiv2;
        odiv3.onclick=fdiv3;
    }
    </script>
//测试结果:点击div3时,依次弹出div3,div2,div1
  function fdiv3(ev){
            var en=ev || event;
            en.cancelBubble=true;
            alert("div3");
        }
//测试结果:点击div3时,只弹出div3
<script>
    window.onload=function (){
    var odiv1=document.getElementById("div1");
    var odiv2=document.getElementById("div2");
    var odiv3=document.getElementById("div3");

    odiv1.addEventListener("click",function(){
        alert("div1");
    },true);
    odiv2.addEventListener("click",function(){
        alert("div2");
    },true);
    odiv3.addEventListener("click",function(){
        alert("div3");
    },true);
}
</script>
// 测试结果:点击div3时,依次弹出div1,div2,div3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# 事件委托

# 简介

事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素DOM的类型,来做出不同的响应。

利用事件冒泡原理可以实现 “事件委托”

所谓事件委托,**就是在父元素上添加事件监听器,用以监听和处理子元素的事件,避免重复为子元素绑定相同的事件。**当目标元素的事件被触发以后,这个事件就从目标元素开始,向最外层元素传递,最终冒泡到父元素上,父元素再通过 event.target 获取到这个目标元素,这样做的好处是,父元素只需绑定一个事件监听,就可以对所有子元素的事件进行处理了,从而减少了不必要的事件绑定,对页面性能有一定的提升。

# 好处

比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

  • 绑定在父元素上只需要绑定一次,节省性能
  • 子元素不需要每个都去绑定同一事件
  • 如果后续又有新的子元素添加,会由于事件委托的原因,自动接收到父元素的事件监听
# 举例

最经典的就是ul和li标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加。比如事件代理就用到了代理模式

<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
<script>
    let ul = document.querySelector('#ul')
    ul.addEventListener('click', (event) => {
        console.log(event.target);
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

因为存在太多的 li,不可能每个都去绑定事件。这时候可以通过给父节点绑定一个事件,让父节点作为代理去拿到真实点击的节点。

# 自定义事件CustomEvent

const ev = document.getElementById('ev');
const event = new CustomEvent('eventName', {
    detail: {
        message: 'Hello World',
        time: new Date(), 
    },
    bubbles: true,
    cancelable: true,
} );
ev.addEventListener('eventName',function(e){
    console.log(e);
},);
setTimeout(function () {
    ev.dispatchEvent(event);//给节点分派一个合成事件
}, 1000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Event事件的用法

  • event.preventDefault() // 阻止默认行为 ; 比如: <a>有默认跳转功能;
  • event.stopPropagation() // 阻止冒泡行为 ;会立即停止对后续节点的访问,但是会执行完绑定到当前节点上的所有事件处理程序
  • event.stopImmediatePropagation() //而调用stopImmediatePropagation函数之后,除了所有后续节点,绑定到当前元素上的、当前事件处理程序之后的事件处理程序就不会再执行了
  • 属性:event.currentTarget // 指的是绑定了事件监听的元素(可以理解为触发事件元素的父级元素);
  • 属性:event.target // 表示当前被点击的元素

# 封装事件工具类

通用的事件侦听器函数及浏览器兼容考虑;

Event = {
  //页面加载完成后
  readyEvent: function(fn) {
    if (fn == null) {
      fn = document;
    } 
    var oldonload = window.onload; 
    if (typeof window.onload != 'function') {
      window.onload = fn; 
    }else{
      window.onload = function() { 
        oldonload(); 
        fn();
      }; 
    }
  }, 
  //视能力分别使用 demo0 || demo1 || IE 方式来绑定事件 //参数:操作的元素,事件名称,事件处理程序 
  addEvent: function(element,type,handler) { 
    if (element.addEventListener) { //事件类型、需要执行的函数、是否捕捉   
      element.addEventListener(type,handler,false); 
    }else if (element.attachEvent) { 
      element.attachEvent('on' + type, function() {
        handler.call(element);
      }); 
    }else { 
      element['on' + type] = handler; 
    }
  }, 
  //移除事件 
  removeEvent: function(element,type,handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type,handler,false); 
    }else if (element.datachEvent) { 
      element.datachEvent('on' + type,handler); 
    }else{
      element['on' + type] = null;
    }
  },
  //阻止事件(主要是事件冒泡,因为IE不支持事件捕获) 
  stopPropagation: function(ev) { 
    if (ev.stopPropagation) { 
      ev.stopPropagation(); 
    }else { 
      ev.cancelBubble = true;
    }
  }, 
  //取消事件的默认行为
  preventDefault: function(event) {
    if (event.preventDefault) { 
      event.preventDefault(); 
    }else{
      event.returnValue = false; 
    }
  }, 
  //获取事件目标 
  getTarget: function(event) { 
    return event.target || event.srcElemnt; 
  },
  //获取event对象的引用,取到事件的所有信息,确保随时能使用event; 
  getEvent: function(e) { 
    var ev = e || window.event;
    if (!ev) { 
      var c = this.getEvent.caller; 
      while(c) { 
        ev = c.argument[0]; 
        if (ev && Event == ev.constructor) {
          break; 
        } 
        c = c.caller; 
      } 
    } 
    retrun ev; 
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

# gesture事件处理复杂手势

DOM事件从PC端的 鼠标事件(mouse) 发展到了 移动端的 触摸事件(touch) 和 手势事件(guesture),touch事件描述了手指在屏幕操作的每一个细节,guesture 则是描述多手指操作时更为复杂的情况,总结如下:

  • 第一根手指放下,触发 touchstart,除此之外什么都不会发生
  • 手指滑动时,触发touchmove
  • 第二根手指放下,触发 gesturestart
  • 触发第二根手指的 touchstart
  • 立即触发 gesturechange
  • 任意手指移动,持续触发 gesturechange
  • 第二根手指弹起时,触发 gestureend,以后将不会再触发 gesturechange
  • 触发第二根手指的 touchend
  • 触发touchstart (多根手指在屏幕上,提起一根,会刷新一次全局touch)
  • 弹起第一根手指,触发 touchend

# 相关事件

# DOMContentLoaded、window.onload、body.onload
  • DOMCOntentLoaded:指文档加载完成触发的事件,即dom加载完成,不用考虑其他资源,例如图片
    • 常见的库中都提供了此事件的兼容各个浏览器的封装,如果你是个jQuery使用者,你可能会经常使用**$(document).ready(); 或者$(function(){}) 这都是使用了DOMContentLoaded事件**; DOMContentLoaded事件就相当于jquery中的ready方法,也就是DOM结构加载完成的事件。
  • onload:当页面载入完毕后执行Javascript代码; 页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了。
  • window.onload和body.onload谁在下面就是谁覆盖谁,只会执行后面的那个。
<script language="javascript">
    if(document.addEventListener){
        function DOMContentLoaded(){
            console.log("window.onload");
            $("#status").text("DOM is ready now!");
        }
        document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );//1
    }
    window.οnlοad=function(){
        onsole.log("DOMContentLoaded");
        $("#status").text("DOM is ready AND wondow.onload is excute!");//2
    }
</script>
<body onload="console.log('bodyonload');">
    <div id="div1">a</div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

执行顺序: 在chrome、IE10和FireFox亲测,执行结果是:DOMContentLoaded然后才是onload的输出所以说一般情况下,DOMContentLoaded事件要在window.onload之前执行,当DOM树构建完成的时候就会执行DOMContentLoaded事件。当window.onload事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了。

**$(document).ready()**下面三个写法是等价的:

  • $(document).ready(handler)
  • $(handler)
  • $().ready(handler) 不推荐;
//window.onload不能同时编写多个。//以下代码无法正确执行,结果只输出第二个。
window.onload = function(){
  alert("test1");
};
window.onload = function(){
  alert("test2");
};

//$(document).ready()能同时编写多个//结果两次都输出
$(document).ready(function(){ 
   alert("Hello World"); 
}); 
$(document).ready(function(){ 
   alert("Hello again"); 
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# mouseover和mouseenter的区别
  • mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout
  • mouseenter当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave
# js拖拽功能的实现

首先是三个事件,分别是mousedown,mousemove,mouseup

  • 当鼠标点击按下的时候,需要一个tag标识此时已经按下,可以执行mousemove里面的具体方法。

    • clientX,clientY标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用offsetX和offsetY来表示元素的元素的初始坐标,移动的举例应该是:鼠标移动时候的坐标-鼠标按下去时候的坐标。
    • 也就是说定位信息为:鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的offetLeft.
  • 还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,我们改变的是绝对定位条件下的left 以及top等等值。

补充:也可以通过html5的拖放(Drag 和 drop)来实现

# 异步(延迟)加载JS的方式

  • defer,只支持IE;
  • async;
  • hack的方式;创建script,插入到DOM中,加载完毕后callBack; 动态创建DOM方式(用得最多);
  • 按需异步载入js;
function loadScript(url, callback){//动态创建脚本;兼容 IE 方案;
  var script = document.createElement("script")
  script.type = "text/javascript";
  if(script.readyState){ //IE
    script.onreadystatechange = function(){
      if (script.readyState == "loaded" ||script.readyState == "complete"){
        script.onreadystatechange = null;
        callback();
      }
    };
  } else { //Others: Firefox, Safari, Chrome, and Opera
    script.onload = function(){
      callback();
    };
  }
  script.src = url;
  document.body.appendChild(script);//appendChild
}

function hackZepto(){//示例:动态加载zepto.js脚本
  var ndParent = document.getElementsByName("script")[0];
  var ndScript = document.createElement("script");
  // ndScript.src = "http://127.0.0.1:8080/dist/zepto.custome-touch.js";
  ndScript.src = "http://zeptojs.com/zepto.js";
  ndScript.onload = function () {
    if (window.Zepto){
      console.log("injected");
      window.$ = window.Zepto;
    }
  };
  ndParent.parentNode.appendChild(ndScript);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# AMD、CMD规范区别

详见模块下的【js模块化】介绍;

# 简介

AMD(Modules/Asynchronous-Definition)、CMD(Common Module Definition) AMD 规范:https://github.com/amdjs/amdjs-api/wiki/AMD requirejs CMD 规范:https://github.com/seajs/seajs/issues/242 seajs

# 区别

1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
2. **AMD 推崇依赖前置,CMD 推崇依赖就近**。看示例:
// AMD 默认推荐
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
  a.doSomething()
  // 此处略去 100 行
  b.doSomething()
  // ...
})
// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此处略去 100 行
  var b = require('./b') // 依赖可以就近书写
  b.doSomething()
  // ...
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# JS代码在HTML文件中方式

主要有三种:行内内部外部

行内方式:

<input type="button" value="点击有惊喜" onclick="javascript:alert('哈哈哈哈')">
1

内部方式:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <script type="text/javascript">
      //声明一个函数(整个文档都可以使用)
      function surprise() {
        alert('恭喜你中了一百万')
      }
    </script>
  </head>
  。。。
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13

外部方式:

<!--很多html页面都可以调用js4.js页面-->
<script src="../../js/js4.js" type="text/javascript" charset="utf-8">
1
2

# 使用 CDN 加载 jQuery 库的主要优势

1:除了报错节省服务器带宽以及更快的下载速度这许多的好处之外,

2:最重要的是,如果浏览器已经从同一个CDN下载类相同的 jQuery 版本, 那么它就不会再去下载它一次. 许多公共的网站都将jQuery用于用户交互和动画, 如果浏览器已经有了下载好的jQuery库,网站就能有非常好的展示机会。

# 利用多个域名来存储网站资源会更有效

  1. CDN缓存更方便; CDN的关键技术主要是内容存储和分布技术。CDN主要用来使用户就近获取资源

  2. 节约cookie带宽;

  3. 节约主域名的连接数,优化页面响应速度;

  4. 防止不必要的安全问题;

  5. 突破浏览器并发限制;

      同一时间针对同一域名下的请求有一定数量限制,超过限制数目的请求会被阻塞。**大多数浏览器的并发数量都控制在6以内**。有些资源的请求时间很长,因而会阻塞其他资源的请求。因此,对于一些静态资源,如果放到不同的域名下面就能实现与其他资源的并发请求。
      
      因而后来衍生了domain dash来加大并发数,但是过多的域名会使DNS解析负担加重,因此一般控制在2-4个。对于图片资源的加载,**利用css sprites技术,结合background的定位在同一张图片中加载多个图片,这也是减少并发数量的一种常用方法。**
    

# Script标签放在body标签的位置

# 脚本位置:

浏览器在解析到<body>标签之前,不会渲染页面的任何部分。把脚本放到页面顶部会导致明显的延迟,通常表现为显示空白页面,用户无法浏览内容,也无法和页面进行交互。

尽管IE8,FX3.5,Safari4和Chrome2+都允许并行下载JavaScript文件。这样<script>标签在下载外部资源时不会阻塞其他<script>标签。遗憾的是,JavaScript下载过程中任然会阻塞其他资源的下载,比如图片。在文档的head元素中包含所有JavaScript文件,意味着必须等到全部JavaScript代码都被下载解析执行完成之后,才能开始呈现页面内容(浏览器在遇到body标记时才开始呈现内容)。

由于脚本会阻塞其他资源的下载,因此推荐将所有<script>标签尽可能放到<body>标签的底部,以尽量减少对整个页面下载的影响。

# 组织脚本:

由于每个<script>标签初始下载时都会阻塞页面渲染,所以减少页面包含<script>标签数量有助于改善这一情况。这**不仅仅针对外链脚本,内嵌脚本的数量也要同样的限制。**浏览器在解析HTML页面的过程中每遇到一个<script>标签,都会因执行脚本而导致一定的延迟,因此最小化延迟时间将会明显地改善页面的性能。

因此下载单个100kb的文件比下载4个25kb的文件更快。

# 无阻塞下载JavaScript的方法

1:延迟的脚本

HTML4为<script>标签定义了一个拓展属性:defer。Defer属性指明本元素所含的脚本不会修改DOM,因此代码能够安全地延迟执行。对应的JavaScript文件将在页面解析到这个<script>标签时开始下载,但不执行,直到DOM下载完成(onload事件被触发前),因而它不会阻塞其他进程,此类文件可以与页面其他资源并行下载。

经过发现只有在外链脚本时defer才起到作用,内嵌脚本没有起到作用,我用的浏览器为Chrome。

没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <script src="../../../Scripts/javascript/PO/defer.js" defer></script>
    <script src="../../../Scripts/javascript/PO/defer2.js"></script>
    <script>
        window.onload = function (event) {
            alert("onload ");
        }
    </script>
</body>
</html>
//defer.js:
//alert("defer")
//defer2.js:
//alert("before onload");

<script defer src="script.js"></script>//(延迟执行)
<script async src="script.js"></script>//(异步下载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

2:动态创建脚本

动态创建script脚本后,文件会在被添加到页面时立即下载。它的重点在于文件的下载和执行过程不会阻塞页面其他进程,在下载文件时里面的代码会立即执行。

这种情况下,当下载的代码包含其他脚本要调用的接口或者方法时,就会有问题,所以我们需要跟踪并确保脚本下载完毕且准备就绪。FX,Opera,Chrome和Safari 3+的版本会在script元素接收完成时触发load事件,因此,你可以通过监听此事件来判断是否下载完毕。

function loadScript(url, callback) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = url;
    script.onload = function () {
        callback();
    };
    document.getElementsByTagName("head")[0].appendChild(script);
}
1
2
3
4
5
6
7
8
9

# JS延迟加载的方法

  • 1、async;<script async src="script.js"></script>:给script标签加async属性,则加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步);需要注意的是,**这种方式加载的 JavaScript 依然会阻塞 load 事件。**换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。

  • 2、defer; <script defer src="script.js"></script>:给script标签加defer属性,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成

  • 3、动态创建script标签:等到DOMContentLoaded 事件触发时,生成一个script标签,渲染到页面上上;

    hack的方式;创建script,插入到DOM中,加载完毕后callBack; 动态创建DOM方式(用得最多);

  • 4、setTimeout定时器延迟代码执行;

  • 5、按需异步载入js;

# async&defer加载的区别

1)情况1 <script src="script.js"></script> 没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。

2)情况2 <script async src="script.js"></script> (异步下载) async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,**这种方式加载的 JavaScript 依然会阻塞 load 事件。**换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。

3)情况3 <script defer src="script.js"></script>(延迟执行) 【推荐】 defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码然后触发 DOMContentLoaded 事件。

defer 与相比普通 script,有两点区别

  • 载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后;

  • 在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。

# 总结

async 表示应该**立即下载脚本,但不应妨碍页面中的其他操作,**比如下载其他资源或等待加载其他脚本。只对外部脚本文件有效。 如果有多个脚本谁先加载完谁先执行,async的加载不计入DOMContentLoaded事件统计。

defer 表示**脚本可以延迟到文档完全被解析和显示之后再执行。**文档解析时,遇到了设置了defer的脚本,就会在后台下载,但是并不会阻止文档的渲染,当页面解析渲染完毕后,会等到所有的defer脚本加载完毕并按照顺序执行,指定完毕后会触发DOMContentLoaded事件。 如果有多个设置了defer的script标签存在,则会按照顺序执行所有的script。在文档渲染完毕后,DOMContentLoaded事件调用前执行。

# 减少 JavaScript 对性能的影响的方法

将所有的script标签放到页面底部,即body闭合标签之前,这能确保在脚本执行前页面已经完成了DOM树渲染。可能地合并脚本。页面中的script标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。 采用无阻塞下载 JavaScript 脚本的方法: (1)使用script标签的 defer 属性仅适用于 IE 和 Firefox 3.5 以上版本); (2)使用动态创建的script元素来下载并执行代码

JS优化<script> 标签加上 defer属性 和 async属性用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。

  • async属性: HTML5新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。异步加载
  • defer属性: 用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。延迟加载【推荐】
  • 在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。

# 与html操作

# 解决跨域问题

jsonp、 iframe、window.name、window.postMessage、服务器上设置代理页面

  1. jsonp跨域访问;
  2. nginx代理跨域;
  3. nodejs中间件代理跨域; 【开发模式的话:有webpack proxy, html-middle, 浏览器代理启动(不能处理cookie)】;
  4. WebSocket协议跨域;
  5. 跨域资源共享(CORS); 普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。
  6. html5中的window.postMessage方法;
  7. http头部信息中加入origin;
  8. window.name + iframe跨域;
  9. document.domain + iframe跨域;
  10. location.hash + iframe;

JSONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;

# 列出一些JS框架

JS框架是用JavaScript编写的应用程序框架,它与控制流中的JS库不同,一些最常用的框架是:

  • Vue
  • React
  • Angular

他们各自的优点和缺点

# JS 中的 prompt 框是什么

提示框是允许用户通过提供文本框输入输入的框。 prompt() 方法显示一个对话框,提示访问者输入。 如果您希望用户在输入页面之前输入值,则通常会使用提示框。 弹出提示框时,用户必须在输入输入值后单击“确定”或“取消”才能继续。

# JavaScript中提取URL的部分

//http://sub.domain.com/virtualPath/page.htm
window.location.host//sub.domain.com:8080或sub.domain.com:80
window.location.hostname  //sub.domain.com
window.location.protocol //http:
window.location.port //8080或80
window.location.pathname ///virtualPath
window.location.origin //http://sub.domain.com*****
1
2
3
4
5
6
7

获取参数示例:

function parseQueryString(url) {
  var params = {}, arr = url.split("?");
  if (arr.length <= 1) return params;
  arr = arr[1].split("&");
  for (var i = 0, l = arr.length; i < l; i++) {
    var a = arr[i].split("=");
    params[a[0]] = a[1];
  }
  return params;
}
var url = "http://baidu.com?key0=0&key1=1&key2=2";
var ps = parseQueryString(url);
console.log(ps);//{ key0: '0', key1: '1', key2: '2' }
console.log(ps["key1"]);//1
1
2
3
4
5
6
7
8
9
10
11
12
13
14

正则方式实现:replace + 分组实现;

//通过replace方法获取url中的参数的方法
(function(pro){
    function queryString(){
        var obj = {}, reg = /([^?&#+]+)=([^?&#+]+)/g;
        this.replace(reg,function($0,$1,$2){
            obj[$1] = $2;
        })
        return obj;
    }
    pro.queryString = queryString;
}(String.prototype));
// 例如 url为 https://www.baidu.com?a=1&b=2
// window.location.href.queryString();
// {a:1,b:2}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 其他相关

# 兼容处理

# IE与其他浏览器不一样的特性

事件不同之处:详细见上面 【自己封装事件类】的实现;

  • 触发事件的元素被认为是目标(target)。而在 IE 中,目标包含在 event 对象的 srcElement 属性;

  • 获取字符代码、如果按键代表一个字符(shift、ctrl、alt除外),IE 的 keyCode 会返回字符代码(Unicode),DOM 中按键的代码和字符是分离的,要获取字符代码,需要使用 charCode 属性;

  • 阻止某个事件的默认行为,IE 中阻止某个事件的默认行为,必须将 returnValue 属性设置为 false,Mozilla 中,需要调用 preventDefault() 方法;

  • 停止事件冒泡,IE 中阻止事件进一步冒泡,需要设置 cancelBubble 为 true,Mozzilla 中,需要调用 stopPropagation();

阻止冒泡的方法

function stopPP(e){
    var evt = e || window.event; //IE用cancelBubble=true来阻止而FF下需要用stopPropagation方法
    evt.stopPropagation? evt.stopPropagation() : (evt.cancelBubble=true);
}
$xxx.click(function(e) {
    e.stopPropagation();
    e.cancelBubble = true;// ie
})
 //阻止事件(主要是事件冒泡,因为IE不支持事件捕获) 
  stopPropagation: function(ev) { 
    if (ev.stopPropagation) { 
      ev.stopPropagation(); 
    }else { 
      ev.cancelBubble = true;
    }
  }, 
  //取消事件的默认行为
  preventDefault: function(event) {
    if (event.preventDefault) { 
      event.preventDefault(); 
    }else{
      event.returnValue = false; 
    }
  }, 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

获取光标的水平位置

function getX(e){
    e = e || window.event; //先检查非IE浏览器,在检查IE的位置
    return e.pageX || e.clentX + document.body.scrollLeft;
}
1
2
3
4

# Polyfill

polyfill 是“在旧版浏览器上复制标准 API 的 JavaScript 补充”,可以动态地加载 JavaScript 代码或库,在不支持这些标准 API 的浏览器中模拟它们。

所有这些都是 W3C 地理位置 API 定义的对象和函数。因为 polyfill 模拟标准 API,所以能够以一种面向所有浏览器未来的方式针对这些 API 进行开发, 一旦对这些 API 的支持变成绝对大多数,则可以方便地去掉 polyfill,无需做任何额外工作。

示例

geolocation(地理位置)polyfill 可以在 navigator 对象上添加全局的 geolocation 对象,还能添加 getCurrentPosition 函数以及“坐标”回调对象,

比如: html5shiv、Geolocation、Placeholder

# 优雅降级和渐进增强

优雅降级

Web站点在所有新式浏览器中都能正常工作,如果用户使用的是老式浏览器,则代码会针对旧版本的IE进行降级处理了,使之在旧式浏览器上以某种形式降级体验却不至于完全不能用。在高级浏览器实现完整功能,然后针对低级浏览器进行hack以便低级浏览器能够正常运行; 一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。 如:border-shadow

渐进增强

从被所有浏览器支持的基本功能开始,逐步地添加那些只有新版本浏览器才支持的功能,向页面增加不影响基础浏览器的额外样式和功能的。当浏览器支持时,它们会自动地呈现出来并发挥作用。 针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。 如:默认使用flash上传,但如果浏览器支持 HTML5 的文件上传功能,则使用HTML5实现更好的体验;

共同:两者是因为各种版本的浏览器对css3的支持情况而不同所造成区别

  • 渐进增强则是从一个非常基础的、能够起作用的版本开始,并不断扩充,以适应未来环境的需要。【增兼前】

  • 而优雅降级是从复杂的现状开始,并试图减少用户体验的供给;【降兼后】

狭义区别:渐进增强一般说的是使用CSS3技术,在不影响老浏览器的正常显示与使用情形下来增强体验,而优雅降级则是体现html标签的语义,以便在js/css的加载失败/被禁用时,也不影响用户的相应功能

.transition { /*渐进增强写法*/
  -webkit-transition: all .5s;
     -moz-transition: all .5s;
       -o-transition: all .5s;
          transition: all .5s;
}
.transition { /*优雅降级写法*/
          transition: all .5s;
       -o-transition: all .5s;
     -moz-transition: all .5s;
  -webkit-transition: all .5s;
}
1
2
3
4
5
6
7
8
9
10
11
12

# 浏览器检测

  • navigator.userAgent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36"
  • 不同浏览器的特性,如addEventListener
//检查是否是微信端;
function isWeiXin() {
  var ua = window.navigator.userAgent.toLowerCase();
  if (ua.match(/MicroMessenger/i) == "micromessenger") {
    return true;
  } else {
    return false;
  }
}
1
2
3
4
5
6
7
8
9

检查各系统浏览器版本;

export default function BrowserType() {
	// 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
	const ua = navigator.userAgent.toLowerCase();
	const testUa = regexp => regexp.test(ua);
	const testVs = regexp => ua.match(regexp)
		.toString()
		.replace(/[^0-9|_.]/g, "")
		.replace(/_/g, ".");
	// 系统
	let system = "unknow";
	if (testUa(/windows|win32|win64|wow32|wow64/g)) {
		system = "windows"; // windows系统
	} else if (testUa(/macintosh|macintel/g)) {
		system = "macos"; // macos系统
	} else if (testUa(/x11/g)) {
		system = "linux"; // linux系统
	} else if (testUa(/android|adr/g)) {
		system = "android"; // android系统
	} else if (testUa(/ios|iphone|ipad|ipod|iwatch/g)) {
		system = "ios"; // ios系统
	}
	// 系统版本
	let systemVs = "unknow";
	if (system === "windows") {
		if (testUa(/windows nt 5.0|windows 2000/g)) {
			systemVs = "2000";
		} else if (testUa(/windows nt 5.1|windows xp/g)) {
			systemVs = "xp";
		} else if (testUa(/windows nt 5.2|windows 2003/g)) {
			systemVs = "2003";
		} else if (testUa(/windows nt 6.0|windows vista/g)) {
			systemVs = "vista";
		} else if (testUa(/windows nt 6.1|windows 7/g)) {
			systemVs = "7";
		} else if (testUa(/windows nt 6.2|windows 8/g)) {
			systemVs = "8";
		} else if (testUa(/windows nt 6.3|windows 8.1/g)) {
			systemVs = "8.1";
		} else if (testUa(/windows nt 10.0|windows 10/g)) {
			systemVs = "10";
		}
	} else if (system === "macos") {
		systemVs = testVs(/os x [\d._]+/g);
	} else if (system === "android") {
		systemVs = testVs(/android [\d._]+/g);
	} else if (system === "ios") {
		systemVs = testVs(/os [\d._]+/g);
	}
	// 平台
	let platform = "unknow";
	if (system === "windows" || system === "macos" || system === "linux") {
		platform = "desktop"; // 桌面端
	} else if (system === "android" || system === "ios" || testUa(/mobile/g)) {
		platform = "mobile"; // 移动端
	}
	// 内核和载体
	let engine = "unknow";
	let supporter = "unknow";
	if (testUa(/applewebkit/g)) {
		engine = "webkit"; // webkit内核
		if (testUa(/edge/g)) {
			supporter = "edge"; // edge浏览器
		} else if (testUa(/opr/g)) {
			supporter = "opera"; // opera浏览器
		} else if (testUa(/chrome/g)) {
			supporter = "chrome"; // chrome浏览器
		} else if (testUa(/safari/g)) {
			supporter = "safari"; // safari浏览器
		}
	} else if (testUa(/gecko/g) && testUa(/firefox/g)) {
		engine = "gecko"; // gecko内核
		supporter = "firefox"; // firefox浏览器
	} else if (testUa(/presto/g)) {
		engine = "presto"; // presto内核
		supporter = "opera"; // opera浏览器
	} else if (testUa(/trident|compatible|msie/g)) {
		engine = "trident"; // trident内核
		supporter = "iexplore"; // iexplore浏览器
	}
	// 内核版本
	let engineVs = "unknow";
	if (engine === "webkit") {
		engineVs = testVs(/applewebkit\/[\d._]+/g);
	} else if (engine === "gecko") {
		engineVs = testVs(/gecko\/[\d._]+/g);
	} else if (engine === "presto") {
		engineVs = testVs(/presto\/[\d._]+/g);
	} else if (engine === "trident") {
		engineVs = testVs(/trident\/[\d._]+/g);
	}
	// 载体版本
	let supporterVs = "unknow";
	if (supporter === "chrome") {
		supporterVs = testVs(/chrome\/[\d._]+/g);
	} else if (supporter === "safari") {
		supporterVs = testVs(/version\/[\d._]+/g);
	} else if (supporter === "firefox") {
		supporterVs = testVs(/firefox\/[\d._]+/g);
	} else if (supporter === "opera") {
		supporterVs = testVs(/opr\/[\d._]+/g);
	} else if (supporter === "iexplore") {
		supporterVs = testVs(/(msie [\d._]+)|(rv:[\d._]+)/g);
	} else if (supporter === "edge") {
		supporterVs = testVs(/edge\/[\d._]+/g);
	}
	// 外壳和外壳版本
	let shell = "none";
	let shellVs = "unknow";
	if (testUa(/micromessenger/g)) {
		shell = "wechat"; // 微信浏览器
		shellVs = testVs(/micromessenger\/[\d._]+/g);
	} else if (testUa(/qqbrowser/g)) {
		shell = "qq"; // QQ浏览器
		shellVs = testVs(/qqbrowser\/[\d._]+/g);
	} else if (testUa(/ucbrowser/g)) {
		shell = "uc"; // UC浏览器
		shellVs = testVs(/ucbrowser\/[\d._]+/g);
	} else if (testUa(/qihu 360se/g)) {
		shell = "360"; // 360浏览器(无版本)
	} else if (testUa(/2345explorer/g)) {
		shell = "2345"; // 2345浏览器
		shellVs = testVs(/2345explorer\/[\d._]+/g);
	} else if (testUa(/metasr/g)) {
		shell = "sougou"; // 搜狗浏览器(无版本)
	} else if (testUa(/lbbrowser/g)) {
		shell = "liebao"; // 猎豹浏览器(无版本)
	} else if (testUa(/maxthon/g)) {
		shell = "maxthon"; // 遨游浏览器
		shellVs = testVs(/maxthon\/[\d._]+/g);
	}
	return Object.assign({
		engine, // webkit gecko presto trident
		engineVs,
		platform, // desktop mobile
		supporter, // chrome safari firefox opera iexplore edge
		supporterVs,
		system, // windows macos linux android ios
		systemVs
	}, shell === "none" ? {} : {
		shell, // wechat qq uc 360 2345 sougou liebao maxthon
		shellVs
	});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

# 兼容浏览器的获取指定元素(elem)的样式属性(name)的方法

function getStyle(elem, name){
  if(elem.style[name]){//如果属性存在于style[]中,直接取
    return elem.style[name];
  }else if(elem.currentStyle){//否则 尝试IE的方法
    return elem.currentStyle[name];
  }else if(document.defaultView && document.defaultView.getComputedStyle){ //尝试W3C的方式
    name = name.replace(/([A-Z])/g, "-$1");//W3C中为textAlign样式,转为text-align
    name = name.toLowerCase();
    var s = document.defaultView.getComputedStyle(elem, "");
    return s && s.getPropertyValue(name);
  }else{
    return null;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# js怎么控制一次加载一张图片,加载完后再加载下一张

方法1:监听onload

<script type="text/javascript">
    var obj=new Image();
    obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
    obj.onload=function(){
        alert('图片的宽度为:'+obj.width+';图片的高度为:'+obj.height);
        document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
    }
</script>
<div id="mypic">onloading……</div> 
1
2
3
4
5
6
7
8
9

方法2:监听onreadystatechange, this.readyState

<script type="text/javascript">
    var obj=new Image();
    obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
    obj.onreadystatechange=function(){
        if(this.readyState=="complete"){
            alert('图片的宽度为:'+obj.width+';图片的高度为:'+obj.height);
            document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
        }
    }
</script> 
<div id="mypic">onloading……</div>
1
2
3
4
5
6
7
8
9
10
11

# 相关链接

https://juejin.cn/post/7017588385615200270

https://juejin.cn/post/6844903592831238157

上次更新: 2022/04/15, 05:41:26
×