跳至主要內容

难点总结

Mr.Chen前端基石JS基础大约 23 分钟约 7023 字

前端五大模块

  • h5CSS3
  • ajax
  • 原生 js
  • 流行框架(vue,rect,augular 等)
  • git/svn

1.字符串拼串的时候记得两边有空格

uls[i].style.transition=" all 1s "+i*0.1+"s";

2.decodeUrI()对地址栏参数进行解码

3.数组的内置方法

  • slice(begin,end)从 begin 开始,到 end 结束,不包含 end,截取数组的一部分,放到一个新数组中返回,原数组不变
  • splice 最牛逼的方法,拼接 可以在任意位置增加元素或删除元素,获取替换元素
    //第一个参数:起始位置
    //第二个参数:删除的个数
    //第三个参数:替换的元素
    var myFish=["angle","clown""mandarin","STURGEON"];

    myFish.splice(2,1);

    //增加元素
    myFish.splice(20"刘备");

4.数组 内置方法

● 数组常用方法

  • 数组转换字符串
//语法:array.join() //作用:将数组的值拼接成字符串
var arr = [1, 2, 3, 4, 5]
arr.join() //不传参数,默认按【,】进行拼接
arr.join('-') //参数是字符串类型 按【-】进行拼接
  • 数组的增删操作
      array.push(value,[value[,...]]);//将一个或多个元素添加到数组的结尾,并返回新的长度。

      array.pop();//从数组中删除最后一个元素,并返回该元素的值,改变了数组的长度

      array.unshift(value,[value[,...]]);//将一个或多个元素添加到数组的开头,并返回新的长度。

      array.shift();//从数组中删除第一个元素,并返回该元素的值,改变了数组的长度
      //练习1
      var arr = ["曹操"];
      //添加数据后变成:["司马懿","曹丕","杨修","曹植","曹操"]
      //删除数据后变成:["司马懿","曹丕"]
      //练习2
      var arr = ["司马懿","曹丕","杨修","曹植","曹操"]
      //把数组的最后一个元素变成数组的第0个元素
      //把数组的第0个元素变成数组的最后一个元素
  • 数组的翻转与排序
array.reverse() //翻转数组,返回翻转过的数组
array.sort() //默认排序顺序是根据字符串Unicode码点。
//如果是数字,10 小于 2 ,比较第一位
var arr = [1, 3, 10, 2, 4, 40, 5]
arr.sort() //[1, 10, 2, 3, 4, 40, 5]

//sort方法可以传递一个函数作为参数,这个参数用来控制数组如何进行排序
var arr = ['ba', 'bb', 'a']
//arr = [1,2,10,4,40,5];

arr.sort(function (a, b) {
  return a - b //按照正序
  //return b-a //按照倒序
})

/*注意:
    如果 compareFunction(a, b) a-b 小于 0 ,那么 a 会被排列到 b 之前;
    如果 compareFunction(a, b) a-b 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且   也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本;
    如果 compareFunction(a, b) a-b 大于 0 , b 会被排列到 a 之前。
       
    */

//练习:
//将[3, 6, 1, 5, 10, 2,11]从小到大排列
//将字符串数组按照字符长度从小到大排列
//将学生数组按照年龄从小到大排列(如何将对象排序)
  • 数组的拼接与截取
      //concat:数组合并,不会影响原来的数组,会返回一个新数组。
      var arr = [1,2,3]
      var arr1 = ["a","b","c"]
      var newArray = arr.concat(arr1);//[1,2,3,"a","b","c"];

      //slice:复制数组的一部分到一个新数组,并返回这个新数组
      //原来的数组不受影响,包含头,不包含尾
      var newArray = array.slice(begin, end);

      var arr = [1,2,3,4,5];
      arr.slice(0,3) //[1,2,3]
      //arr.slice(-3,-1) //[3,4]

      //splice: 以新元素来替换旧元素,以此来修改数组的内容,返回被替换的内容,原数组被改变
      //start:开始位置  deleteCount:删除的个数  items:替换的内容
      array.splice(start, deleteCount, [items[,items...]);

      var arr = [1,2,3,4,5]
      var newArray = arr.splice(0,3,"a","b","c","d")
      console.log(newArray)//[1, 2, 3]
      console.log(arr)// ["a", "b", "c", "d", 4, 5]

   //练习:
    var arr = [4, 6, 7, 8, 3, 46, 8];
    //从数组中截取一个新的数组[6,7,8,3],返回新的数组
    //删除[6,7,8,3]并替换成[1,1,1]
  • 数组查找元素
//indexOf方法用来查找数组中某个元素第一次出现的位置,如果找不到,返回-1
array.indexOf(search, [fromIndex])
//lastIndexOf()从后面开始查找数组中元素出现位置,如果找不到,返回-1
array.lastIndexOf(search, [fromIndex])
  • 操作数组里的元素
    var arr = [12,34,56,89,78,23,45,19];
    
    • 1.filter 方法返回一个由符合函数要求的元素组成的新数组调用数组的 filter 方法,添加过滤方法,符合规则的元素会被存放到新数组里
      • element:表示数组里的元素;
      • index:表示索引值;
      • array:表示传入的数组
    filter 方法的数组:
var newArr = arr.filter(function (element, index, array) {
  return element > 30 //返回true表示保留该元素(通过测试),false则不保留。
})
console.log(arr) //filter方法不会改变原数组里的数据[12,34,56,89,78,23,45,19];
console.log(newArr) //新数组里保存符合要求的元素[34, 56, 89, 78, 45]
  • 2.map 方法让数组中的每个元素都调用一次提供的函数,将调用的后的结果存放到一个新数组里并返回。
    newArr = arr.map(function(element,index,array)){
          //在数组里的每一个元素的最后面都添加一个字符串"0"
      		return element + "0";
    });
    console.log(newArr);  //["120", "340", "560", "890", "780", "230", "450", "190"]
    console.log(arr);    //map方法不会改变原数组里的数据 [12,34,56,89,78,23,45,19]
  • 3.forEach() 方法对数组的每个元素执行一次提供的函数,且这个函数没有返回值
var result = arr.forEach(function (element, index, array) {
  //数组里的每一个元素都会被打印
  console.log('第' + index + '个元素是' + element)
})
console.log(result) //函数没有返回值
  • 4.some() 方法测试数组中的某些元素是否通过由提供的函数实现的测试.
result = arr.some(function (element, index, array) {
  //数组里否有一些元素大于50.只要有一个元素大于,就返回true
  console.log(element) //12,34,56
  return element > 50
})
console.log(result) //true
  • 5.every() 方法测试数组的所有元素是否都通过了指定函数的测试。
result = arr.every(function (element, index, array) {
  //数组里是否每一个元素都大于50.只有在所有的数都大于50时,才返回true
  console.log(element) //12.  第0个数字就已经小于50了,就没有再比较的意义了
  return element > 50
})
console.log(result) //false
  • 6.findIndex()
    • findIndex 用来查找数组中满足 条件的项的索引号
    • 参数:是一个回调函数,回调函数的参数 item 表示数组中的每一项元素
    • 如果 回调函数的返回值为 true,那 么,findIndex() 就会把当前项的索引号返回, 用法与 forEach 类似
var index = this.list.findIndex(function (item) {
  return item.id === id
})
var index = this.list.findIndex(item => item.id === id)
注意: findIndex() 对于空数组,函数是不会执行的。
注意: findIndex() 并没有改变数组的原始值。

5.history 对象

  • history.back ( ) 后退跟点击浏览器后退按钮一个效果
  • history.forward( )前进
  • history.go( 1 ) 前进 - ##### history.go(-1)后退

javscript:伪协议,页面不跳转,可以执行 js 代码

    <a href=“JavaScript:history.go(-1);"></a> 页面后退

     <a href="javascript:location.reload();" class="icon_right">
      <span class="mui-icon mui-icon-reload"></span>
    </a>
    location.reload()  //页面刷新

6.字符串内置方法

1 字符方法
charAt() //获取指定位置处字符
charCodeAt() //获取指定位置处字符的ASCII码
str[0] //HTML5,IE8+支持 和charAt()等效
2 字符串操作方法 (常用)
  • 查找指定字符串
//indexOf:获取某个字符第一次出现的位置,如果没有,返回-1
//lastIndexOf:从后面开始查找第一次出现的位置。如果没有,返回-1
  • 去除空白 trim();//去除字符串两边的空格,内部空格不会去除
  • 大小写转换
toUpperCase() //全部转换成大写字母
toLowerCase() //全部转换成小写字母
  • 字符串拼接与截取

    • 字符串拼接

      1.可以用 concat,用法与数组一样(返回新的字符串),但是字符串拼串我们一般都用 + 号

    • 字符串截取
      字符串截取的方法有很多,记得越多,越混乱,因此就记好用的就行

//slice :从start开始,end结束,包含头,不包含尾 ,返回一个新的字符串,原字符串不变
//substring :从start开始,end结束,包含头,不包含尾  ,返回一个新的字符串,原字符串不变
// substr : :从start开始,截取length个字符。(推荐)
  • 字符串切割
//split:将字符串分割成数组,原字符串不改变(很常用)
var str = '张三,李四,王五'
var arr = str.split(',')
  • 字符串替换
replace(searchValue, replaceValue) //参数:searchValue:需要替换的值    replaceValue:用来替换的值
//注意,返回一个新的字符串,原来的字符串没有改变
var str = 'abcd'
var newStr = str.replace('d', 'aaaa')
console.log(str) //abcd
console.log(newStr) //abcaaaa

fromCharCode()
// String.fromCharCode(101, 102, 103);	 //把ASCII码转换成字符串

7、字符串与数组常用方法区别

方法数组字符串特点
拼接转字符串 arr.concat()string.concat()连接两个或更多的数组或字符串,并返回结果。不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
查找row 2 col 2indexOf/lastIndexOf

8、tap 事件

  • tap 事件在用户轻击一个元素时触发。tap 事件类似于 jQuery click() 方法。
  • mui 组件下拉刷新阻止了 click 事件,在移动端中可以替代 click 事件,
  • e.dataset 原生 js 获取自定义属性 所有 data-开头的属性都会存储到 dataset 中

9、js 小数进度丢失

num.toFixed(2) //四舍五入后保留几位小数

10、移动端自适应布局--流式布局

11、移动端自适应布局--响应式布局

// 1 获取屏幕的宽度
// 2 设置设计图的初始大小
// 3 设置需要的font-size
// 4 获取初始比例(2/3)
// 5 根据当前屏幕大小设置对应的fontsize
// 6 针对屏幕做出限定, 最小320最大设计图
// 7 设置html的字体大小

rem(750, 40)

function rem(uiWidth, fonts) {
  var html = document.documentElement
  var screenWidth = html.clientWidth
  // var uiWidth = uiWidth
  var fonts = fonts
  var proportion = uiWidth / fonts
  var timer = null

  getRem()

  window.onresize = getRem

  function getRem() {
    clearTimeout(timer)
    timer = setTimeout(function () {
      screenWidth = html.clientWidth
      if (screenWidth <= 320) {
        html.style.fontSize = 320 / proportion + 'px'
      } else if (screenWidth >= uiWidth) {
        html.style.fontSize = uiWidth / proportion + 'px'
      } else {
        html.style.fontSize = screenWidth / proportion + 'px'
      }
    }, 100)
    console.log(getComputedStyle(html).fontSize)
  }
}

12、全屏兼容

// 兼容性处理
full.addEventListener('click', function () {
  // 将来有可能直接有这个方法, 就直接调用
  if (video.requestFullScreen) {
    video.requestFullScreen()
  }
  // 对 chrome 做兼容
  else if (video.webkitRequestFullScreen) {
    video.webkitRequestFullScreen()
  }
  // firefox 兼容
  else if (video.mozRequestFullScreen) {
    video.mozRequestFullScreen()
  }
})

13、自定义滚动条

/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/

::-webkit-scrollbar {
  width: 16px;
  height: 16px;
  background-color: #f5f5f5;
}

/*定义滚动条轨道 内阴影+圆角*/

::-webkit-scrollbar-track {
  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
  border-radius: 10px;
  background-color: #f5f5f5;
}

/*定义滑块 内阴影+圆角*/

::-webkit-scrollbar-thumb {
  border-radius: 10px;
  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
  background-color: pink;
}

14、console 家族

console.assert(expression, object[, object...])
//接收至少两个参数,第一个参数的值或返回值为false的时候,将会在控制台上输出后续参数的值。例如:

console.assert(1 == 1, object) // 无输出,返回 undefined
console.assert(1 == 2, object) // 输出 object
console.count()
;(function () {
  for (var i = 0; i < 5; i++) {
    console.count('count')
  }
})()
// count: 1
// count: 2
// count: 3
// count: 4
// count: 5
console.dir(object)
将传入对象的属性,包括子对象的属性以列表形式输出,例
如:
    var obj = {
      name: 'classicemi',
      college: 'HUST',
      major: 'ei'
    };
    console.dir(obj);
    输出:
    Object
    name: 'classicemi',
      college: 'HUST',
      major: 'ei'
console.error(object[, object...])
用于输出错误信息,用法和常见的console.log一样,不同点在于输出内容会标记为错误的样式,便于分辨。
console.group
这是个有趣的方法,它能够让控制台输出的语句产生不同的层级嵌套关系,每一个console.group()会增加一层嵌套,相反要减少一层嵌套可以使用console.groupEnd()方法。语言表述比较无力,看代码:

和console.group()相似的方法是console.groupCollapsed()作用相同,不同点是嵌套的输出内容是折叠状态,在有大段内容输出的时候使用这个方法可以使输出版面不至于太长。。。吧
console.info(object[, object...])
此方法与之前说到的console.error一样,用于输出信息,没有什么特别之处。

console.info('info'); // 输出 info
console.table()
可将传入的对象,或数组以表格形式输出,相比传统树形输出,这种输出方案更适合内部元素排列整齐的对象或数组,不然可能会出现很多的 undefined。
var obj = {
  foo: {
    name: 'foo',
    age: '33'
  },
  bar: {
    name: 'bar',
    age: '45'
  }
}

var arr = [
  ['foo', '33'],
  ['bar', '45']
]

console.table(obj)
console.table(arr)
console.profile([profileLabel])
  可用于性能分析。在 JS 开发中,我们常常要评估段代码或是某个函数的性能。在函数中手动打印时间固然可以,但显得不够灵活而且有误差。借助控制台以及console.profile()方法我们可以很方便地监控运行性能。
function parent() {
  for (var i = 0; i < 10000; i++) {
    childA()
  }
}

function childA(j) {
  for (var i = 0; i < j; i++) {}
}

console.profile('性能分析')
parent()
console.profileEnd()
console.time(name)
计时器,可以将成对的console.time()和console.timeEnd()之间代码的运行时间输出到控制台上,name参数可作为标签
名。
console.time('计时器')
for (var i = 0; i < 1000; i++) {
  for (var j = 0; j < 1000; j++) {}
}
console.timeEnd('计时器')
console.trace()
console.trace()用来追踪函数的调用过程。在大型项目尤其是框架开发中,函数的调用轨迹可以十分复杂,console.trace()方法可以将函数的被调用过程清楚地输出到控制台上。
function tracer(a) {
  console.trace()
  return a
}

function foo(a) {
  return bar(a)
}

function bar(a) {
  return tracer(a)
}

var a = foo('tracer')
console.warn(object[, object...])

输出参数的内容,作为警告提示。

console.warn('warn') // 输出 warn
占位符
console对象上的五个直接输出方法,console.log(),console.warn(),console.error(),console.exception()(等同于console.error())和console.info(),都可以使用占位符。支持的占位符有四种,分别是字符(%s)、整数(%d 或 %i)、浮点数(%f)和对象(%o)。
console.log('%s是%d年%d月%d日', '今天', 2014, 4, 15)
console.log('圆周率是%f', 3.14159)

var obj = {
  name: 'classicemi'
}
console.log('%o', obj)

15、ASCII、Unicode 和 UTF-8 编码的区别

编码大小支持语言
ASCII1 个字节英文
Unicode2 个字节(生僻字 4 个)所有语言
UTF-81-6 个字节,英文字母 1 个字节,汉字 3 个字节,生僻字 4-6 个字节所有语言

16、screen client page 区别

screenX

screen 顾名思义是屏幕,是用来获取鼠标点击位置到屏幕显示器的距离,距离的最大值需根据屏幕分辨率的尺寸来计算。

兼容性:所有游览器都支持此属性。

clientX

event.clientX、event.clientY 就是用来获取鼠标距游览器显示窗口的长度。

image 兼容性:IE 和主流游览器都支持。

offsetX offsetY

event.offsetX event.offsetY

offset 意为偏移量,是被点击的元素距左上角为参考原点的长度,而 IE、FF 和 Chrome 的参考点有所差异。

Chrome 下,offsetX offsetY 是包含边框的,如图所示。 image

而 IE、FF 是不包含边框的,如果鼠标进入到 border 区域,为返回负值,如图所示。 image

兼容性:IE9+,chrome,FF 都支持此属性。

pageX

就是参照点也是浏览器内容区域的左上角,但它不会随着滚动条而变动

注意:在 IE 中没有 pageX、pageY 取而代之的是 event.x、evnet.y。x 和 y 在 webkit 内核下也实现了,所以火狐不支持 x,y。

兼容性:IE 不支持,其他高级游览器支持。 image

17.typeof 和 instranceof 的区别

  • typeof(基本数据类型) 判断变量数据类型
    • typeof 基本类型 返回的是字符串
    • typeof 对象 返回的是 object
    • typeof 数组 返回的是 object
    • typeof 函数 返回的是 function
    • typeof null 返回的 object
  • instanceof(复杂数据类型)
    • instanceof 运算符用来判断一个构造函数的 prototype 属性所指向的对象是否存在另外一个要检测对象的原型链上
    • 语法: obj instanceof Object;//true 实例 obj 在不在 Object 构造函数 中
  • instanceof  运算符用来测试一个对象在其原型链中是否存在一个构造函数的  prototype  属性。其意思就是判断对象是否是某一数据类型(如 Array)的实例,请重点关注一下是判断一个对象是否是数据类型的实例。在这里字面量值,2, true ,'str'不是实例,所以判断值为 false。

18.JavaScript 异步编程__“回调地狱”的一些解决方案

  • 异步编程在 JavaScript 中非常重要。 过多的异步编程也带了回调嵌套的问题,本文会提供一些解决“回调地狱”的方法。

  • 不论是浏览器中最为常见的 ajax、事件监听,还是 node 中文件读取、网络编程、数据库等操作,都离不开异步编程。在异步编程中,许多操作都会放在回调函数(callback)中。同步与异步的混杂、过多的回调嵌套都会使得代码变得难以理解与维护,这也是常受人诟病的地方。先看下面这段代码:

fs.readFile('./sample.txt', 'utf-8', (err, content) => {
  let keyword = content.substring(0, 5)
  db.find(`select * from sample where kw = ${keyword}`, (err, res) => {
    get(`/sampleget?count=${res.length}`, data => {
      console.log(data)
    })
  })
})
首先我们读取的一个文件中的关键字 keyword,然后根据该 keyword 进行数据库查询,最后依据查询结果请求数据。
其中包含了三个异步操作:
  • 文件读取:fs.readFile
  • 数据库查询:db.find
  • http 请求:get 可以看到,我们没增加一个异步请求,就会多添加一层回调函数的嵌套,这段代码中三个异步函数的嵌套已经开始使一段本可以语言明确的代码编程不易阅读与维护了。接下来会介绍一些方法来规避回调地狱。

一、拆解 function
  • 回调嵌套所带来的一个重要问题就是代码不易阅读与维护。因为普遍来说,过多的缩进(嵌套)会极大的影响代码的可读性。基于这一点,可以进行一个最简单的优化——将各步拆解为单个的 function
function getData(count) {
  get(`/sampleget?count=${count}`, data => {
    console.log(data)
  })
}

function queryDB(kw) {
  db.find(`select * from sample where kw = ${kw}`, (err, res) => {
    getData(res.length)
  })
}

function readFile(filepath) {
  fs.readFile(filepath, 'utf-8', (err, content) => {
    let keyword = content.substring(0, 5)
    queryDB(keyword)
  })
}

readFile('./sample.txt')
可以看到,通过上面的改写方式,代码清晰了许多。该方法非常简单,具有一定的效果,但是缺少通用性。

二、事件发布/监听模式
  • 如果在浏览器中写过事件监听 addEventListener,那么你对这种事件发布/监听的模式一定不陌生。

  • 借鉴这种思想,一方面,我们可以监听某一事件,当事件发生时,进行相应回调操作;另一方面,当某些操作完成后,通过发布事件触发回调。这样就可以将原本捆绑在一起的代码解耦。

const events = require('events')
const eventEmitter = new events.EventEmitter()

eventEmitter.on('db', (err, kw) => {
  db.find(`select * from sample where kw = ${kw}`, (err, res) => {
    eventEmitter('get', res.length)
  })
})

eventEmitter.on('get', (err, count) => {
  get(`/sampleget?count=${count}`, data => {
    console.log(data)
  })
})

fs.readFile('./sample.txt', 'utf-8', (err, content) => {
  let keyword = content.substring(0, 5)
  eventEmitter.emit('db', keyword)
})
三、Promise
  • Promise 是一种异步解决方案,最早由社区提出并实现,后来写进了 es6 规范。
  • 目前一些主流的浏览器已经原生实现了 Promise 的 API,可以在 Can I use 里查看浏览器的支持情况。当然,如果想要做浏览器的兼容,可以考虑使用一些 Promise 的实现库,例如 bluebird、 Q 等。下面以 bluebird 为例:
  • 首先,我们需要将异步方法改写为 Promise,对于符合 node 规范的回调函数(第一个参数必须是 Error),可以使用 bluebird 的 promisify 方法。该方法接收一个标准的异步方法并返回一个 Promise 对象。
const bluebird = require('bluebird')
const fs = require('fs')
const readFile = bluebird.promisify(fs.readFile)

这样,readFile 就变成了一个 Promise 对象。 但是,有的异步方法无法进行转换,或者我们需要使用原生 Promise,这就需要我们手动进行一些改造。下面提供一种改造的方法。 ** 以 fs.readFile 为例,借助原生 Promise 来改造该方法:**

const readFile = function (filepath) {
  let resolve, reject
  let promise = new Promise((_resolve, _reject) => {
    resolve = _resolve
    reject = _reject
  })
  let deferred = {
    resolve,
    reject,
    promise
  }
  fs.readFile(filepath, 'utf-8', function (err, ...args) {
    if (err) {
      deferred.reject(err)
    } else {
      deferred.resolve(...args)
    }
  })
  return deferred.promise
}
  • 我们在方法中创建了一个 Promise 对象,并在异步回调中根据不同的情况使用 reject 与 resolve 来改变 Promise 对象的状态。该方法返回这个 Promise 对象。其他的一些异步方法也可以参照这种方式进行改造。
  • 假设通过改造,readFile、queryDB 与 getData 方法均会返回一个 Promise 对象。代码就变为了:
readFile('./sample.txt')
  .then(content => {
    let keyword = content.substring(0, 5)
    return queryDB(keyword)
  })
  .then(res => {
    return getData(res.length)
  })
  .then(data => {
    console.log(data)
  })
  .catch(err => {
    console.warn(err)
  })
可以看到,之前的嵌套操作编程了通过 then 连接的链式操作。代码的整洁度上有了一个较大的提高。

4 generator

  • generator 是 es6 中的一个新的语法。在 function 关键字后添加*即可将函数变为 generator。
const gen = function* () {
  yield 1
  yield 2
  return 3
}
执行 generator 将会返回一个遍历器对象,用于遍历 generator 内部的状态。
let g = gen()
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.next() // { value: 3, done: true }
g.next() // { value: undefined, done: true }
  • 可以看到,generator 函数有一个最大的特点,可以在内部执行的过程中交出程序的控制权,yield 相当于起到了一个暂停的作用;而当一定情况下,外部又将控制权再移交回来。
  • 想象一下,我们用 generator 来封装代码,在异步任务处使用 yield 关键词,此时 generator 会将程序执行权交给其他代码,而在异步任务完成后,调用 next 方法来恢复 yield 下方代码的执行。以 readFile 为例,大致流程如下:
//我们的主任务——显示关键字
//使用yield暂时中断下方代码执行
//yield后面为promise对象
const showKeyword = function* (filepath) {
  console.log('开始读取')
  let keyword = yield readFile(filepath)
  console.log(`关键字为${filepath}`)
}

// generator的流程控制
let gen = showKeyword()
let res = gen.next()
res.value.then(res => gen.next(res))
  • 在主任务部分,原本 readFile 异步的部分变成了类似同步的写法,代码变得非常清晰。而在下半部分,则是对于什么时候需要移交回控制权给 generator 的流程控制。
  • 然而,我们需要手动控制 generator 的流程,如果能够自动执行 generator——在需要的时候自动移交控制权,那么会更加具有实用性。
  • 为此,我们可以使用 co 这个库。它可以是省去我们对于 generator 流程控制的代码
    const co = reuqire('co');
    // 我们的主任务——显示关键字
    // 使用yield暂时中断下方代码执行
    // yield后面为promise对象
    const showKeyword = function* (filepath) {
    console.log('开始读取');
    let keyword = yield readFile(filepath);
    console.log(`关键字为${filepath}`);
    }

    // 使用co
    co(showKeyword);
    其中,yeild关键字后面需要是functio, promise, generator, array或object。可以改写文章一开始的例子:

    const co = reuqire('co');

    const task = function* (filepath) {
       let keyword = yield readFile(filepath);
       let count = yield queryDB(keyword);
       let data = yield getData(res.length);
       console.log(data);
    });

    co(task, './sample.txt');

五、async/await
可以看到,上面的方法虽然都在一定程度上解决了异步编程中回调带来的问题。然而
  • function 拆分的方式其实仅仅只是拆分代码块,时常会不利于后续维护;
  • 事件发布/监听方式模糊了异步方法之间的流程关系;
  • Promise 虽然使得多个嵌套的异步调用能够通过链式的 API 进行操作,但是过多的 then 也增加了代码的冗余,也对阅读代码中各阶段的异步任务产生了一定干扰;
  • 通过 generator 虽然能提供较好的语法结构,但是毕竟 generator 与 yield 的语境用在这里多少还有些不太贴切。
因此,这里再介绍一个方法,它就是 es7 中的 async/await。
  • 简单介绍一下 async/await。基本上,任何一个函数都可以成为 async 函数,以下都是合法的书写形式:
async function foo() {}
const foo = async function () {}
const foo = async () => {}
//在async函数中可以使用await语句。await后一般是一个Promise对象。

async function foo() {
  console.log('开始')
  let res = await post(data)
  console.log(`post已完成,结果为:${res}`)
}
  • 当上面的函数执行到 await 时,可以简单理解为,函数挂起,等待 await 后的 Promise 返回,再执行下面的语句。
  • 值得注意的是,这段异步操作的代码,看起来就像是“同步操作”。这就大大方便了异步代码的编写与阅读。下面改写我们的例子。
    const printData = async function (filepath) {
       let keyword = await readFile(filepath);
       let count = await queryDB(keyword);
       let data = await getData(res.length);
       console.log(data);
    });

printData('./sample.txt');
  • 可以看到,代码简洁清晰,异步代码也具有了“同步”代码的结构。
  • 注意,其中 readFile、queryDB 与 getData 方法都需要返回一个 Promise 对象。这可以通过在第三部分 Promise 里提供的方式进行改写。
后记
  • 异步编程作为 JavaScript 中的一部分,具有非常重要的位置,它帮助我们避免同步代码带来的线程阻塞的同时,也为编码与阅读带来了一定的困难。过多的回调嵌套很容易会让我们陷入“回调地狱”中,使代码变成一团乱麻。为了解决“回调地狱”,我们可以使用文中所述的这五种常用方法:
  • function 拆解

  • 事件发布/订阅模式

  • Promise

  • Generator

  • async / await 理解各类方法的原理与实现方式,了解其中利弊,可以帮助我们更好得进行异步编程。

autocomplete

formdata

多行溢出 两行省略

overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
六、window.event.returnValue=false
  • window.event.returnValue=false 放在提交表单中的 onclick 事件中则不会提交表单,如果放到超链接中则不执行超链接,也就是它禁止了或取消了请求,没有任何效果。
function delItem(id, CheckOutTime) {
  if (confirm('确认要删除订单吗')) {
    $.ajax({
      url: 'http://cangdu.org:8001/v1/cities',
      type: 'post',
      data: {
        type: 'guess'
      },
      success(res) {
        console.log(res)
      }
    })
  } else {
    //window.event.returnValue=false放在提交表单中的onclick事件中则不会提交表单,如果放到超链接中则不执行超链接,也就是它禁止了或取消了请求,没有任何效果。
    window.event.returnValue = false
  }
}
七、event.cancelBubble=true 取消事件处理
<tr>
  <a href="xxx">连接</a>
</tr>

如上结构,单击tr的时候跳转至另一页面

<tr
  style="cursor:pointer"
  onmouseover="this.style.backgroundColor='gainsboro'"
  onmouseout="this.style.backgroundColor=''"
  onclick="return Click();"
>
  function Click() { window.location.href = "xxx"; }

  <a href="xxx">连接</a>
  可更改为<a href="xxx" onclick="event.cancelBubble=true">连接</a>
</tr>

这样可以避免单击 a 标签的同时也跳转至另一页面。onclick="event.cancelBubble=true" 取消事件处理。

否则单击 a 的同时会跳转另一页面。

解析:

取消事件冒泡,在 IE 的事件机制中,触发事件会从子元素向父元素逐级上传,就是说,如果子元素触发了单击事件,那么也会触发父元素的单击事件;event.cancelBubble=true;可以停止事件继续上传

补充一点,Ie 的事件传递是从下到上的:

事件来源对象->上级对象->上上级对象->.....->body->document->window

NS 的事件传递是从上到下:

  • window->document->body->....->事件来源对象

(event.returnValue=false 设置事件的返回值为 false,即取消事件处理)

上次编辑于: