title: JS基础
date: 2019-08-19 13:39:26
tags: javascript
categories:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级
JS 对象
对象,就是一种无序的数据集合,由若干个“键值对”(key-value)构成。
{key: value}是 JS 对象字面量写法
数据类型
number, boolean, string, undefined, null, object, symbol
原始类型,又叫基本类型,值类型,number, string, boolean,undefined,null。指保存在栈内存里的简单数据段
引用类型,对象 object。又可分为狭义的对象,数组,函数,正则。指保存在堆内存中的对象
其中 js 的 number 类型是浮点类型,不是整型.并且浮点类型基于 IEEE 754 标准实现,在使用中会遇到某些 Bug。
NaN 属于 number 类型,NaN表示Not a Number,并且 NaN 不等于自身.
undefined: 表示未定义或不存在.由于没有定义,所以目前没有值
null: 表示无值.即此处的值就是”无”的状态.
typeof
typeof 对于基本类型,除了 null 都可以显示正确的类型
typeof undefined 输出 undefined
typeof []// ‘object’
typeof {} // ‘object’
typeof function(){} //‘function’(这是具体的判断)
typeof null 输出 object
PS:为什么会出现这种情况呢?因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。
运算符
&& (And) 
|| (or) 
! (not) 
1 2 3
   | a = 1 && 3;  a = 1 || 3;  !2 * 0; 
   | 
 
instanceof运算符,判断是否为某一种的实例.
== 和 ===,前者先转化类型,后进行判断,后者比较类型和值
三目运算符
condition ? true case : false case
四则运算符
只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。其他运算只要其中一方是数字,那么另一方就转为数字。并且加法运算会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串。
1 2 3 4 5 6 7 8 9 10 11 12
   | 加法拼接两个字符串 console.log("2"+"4")  加法将其中不是字符串的一方转化为字符串进行拼接 console.log(2+"4")  只有一个字符串参数时将其转化成数字 console.log(+"4")  console.log(+new Date())  字符串加对象会把对象变成[object Object] console.log('aa:'+{a:1}) 
  在参数有对象的情况下调用其valueof或toString,其中valueof优先级更高 console.log(2+new Date()) 
   | 
 
valueof()返回适合该对象类型的原始值。
toString()将该对象的原始值以字符串形式返回。
这两个方法一般是交由 js 去隐式调用。
在数值运算中,优先调用 valueof().
在字符串运算中,优先调用 toString().
自增运算符: a 或 a
1 2
   | (a = 1), (b = a++);  (a = 1), (b = ++a); 
   | 
 
无论是先加,还是后加,a 的值计算后一定会加 1;
1 2
   | (a = 3), (b = 4); a++ + b; 
   | 
 
逗号运算符
取后一个表达式的值.也就是说,前一个会被覆盖.
1
   | var d = ((a = 1), (b = 2)); 
   | 
 
boolean 值
false: undefined null 0 NaN 空字符串
除此之外,其他所有值都转化为 true,包括所有对象.
==比较相等性
相等操作符会先转换操作数(通常称为强制转型),然后比较它们的相等性。
在转换不同的数据类型时,相等操作符遵循下列基本规则:
- 如果有一个操作数是布尔值,则在比较相等性之前,将其转换为数值;
 
- 如果一个操作数是字符串,另一个操作数是数值,在比较之前先将字符串转换为数值;
 
- 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf() 方法,用得到的基本类型值按照前面的规则进行比较;
 
- 如果有一个操作数是 NaN,无论另一个操作数是什么,相等操作符都返回 false;
 
- 如果两个操作数都是对象,则比较它们是不是同一个对象。如果指向同一个对象,则相等操作符返回 true;
 
- 在比较相等性之前,不能将 null 和 undefined 转成其他值。
 
- null 和 undefined 是相等的。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | [] == []  {} == {}  声明引用类型的时候,变量名保存在 js 的栈内存里面,而对应的值保存在堆内存里面 而这个变量在栈内存中实际保存的是:这个值在堆内存中的地址,也就是指针. 如果 a = {} b = {} 虽然 a 和 b 都保存了一个 Object,但这是两个独立的 Object,它们的地址是不同的. 再结合前面的第5条规则:如果两个对象指向同一个对象,相等操作符返回 true 所以 {} == {} 的结果是 false,同样的, [] == [] 的结果也是 false
  [] == ![]  按优先级,先判断![],是false. =>[] == false.[]是对象,调用[].toString返回"",false转化成0. =>"" == 0.Number("")是0,那么结果就是true. {} == !{}  与上面类似,不过Number({})返回 NaN "hello" == true  "hello" == false 
   | 
 
JSON 格式(javascript object notation)
- 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
 
- 简单类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和 null(不能使用 NaN, Infinity, -Infinity 和 undefined)。
 
- 字符串必须使用双引号表示,不能使用单引号。
 
- 对象的键名必须放在双引号里面。
 
- 数组或对象最后一个成员的后面,不能加逗号。
 
JSON.stringify
用于将一个值转为字符串。该字符串符合 JSON 格式,并且可以被 JSON.parse 方法还原。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | JSON.stringify("abc");  JSON.stringify(1);  JSON.stringify(false);  JSON.stringify([]);  JSON.stringify({}); 
  JSON.stringify([1, "false", false]);
 
  JSON.stringify({ name: "张三" });
  var arr = [undefined, function () {}]; JSON.stringify(arr); 
  JSON.stringify(/foo/); 
 
  | 
 
上面代码将各种类型的值,转成 JSON 字符串。
需要注意的是,对于原始类型的字符串,转换结果会带双引号。
如果原始对象中,有一个成员的值是undefined、函数或 XML 对象,这个成员会被过滤。
如果数组的成员是undefined、函数或 XML 对象,则这些值被转成null。
正则对象会被转成空对象。
JSON.parse()
用于将 JSON 字符串转化成对象。
1 2 3 4 5 6 7 8
   | JSON.parse("{}");  JSON.parse("true");  JSON.parse('"foo"');  JSON.parse('[1, 5, "false"]');  JSON.parse("null"); 
  var o = JSON.parse('{"name": "张三"}'); o.name; 
  | 
 
JSON.parse 只能解析字符串,下面这种情况会报错.
1 2 3 4 5 6
   | var str = { name: "张三" }; var o = JSON.parse(str); o.name;  解决方法: var str = '{"name": "张三"}'; 或者; var o = JSON.parse("str");
  | 
 
JavaScript 对象的字面量写法只是长的像 JSON 格式数据,二者属于不同的范畴,JavaScript 对象中很多类型(函数、正则、Date) JSON 格式的规范并不支持,JavaScript 对象的字面量写法更宽松。
写 JavaScript 的基本规范
1.不要在同一行声明多个变量。
2.请使用 ===/!==来比较 true/false 或者数值
3.使用对象字面量替代 new Array 这种形式
4.不要使用全局函数。
5.Switch 语句必须带有 default 分支
6.函数不应该有时候有返回值,有时候没有返回值。
7.For 循环必须使用大括号
8.If 语句必须使用大括号
9.for-in 循环中的变量 应该使用 var 关键字明确限定作用域,从而避免作用域污染。
JS 函数
函数会声明前置
1 2 3 4
   | foo();  function foo() {   return "hello"; }
   | 
 
函数表达式不会
1 2 3 4
   | fn() var fn() = fnction(){    return "hello" }
   | 
 
1 2 3 4 5 6 7 8
   | var str = "一号"; test(); function test() {   console.log(str);   var str = "二号";   console.log(str); } console.log(str);
   | 
 
输出
js 对象查看所有属性
1 2
   | var obj = { p: 1 }; Object.keys(obj);
  | 
 
删除命令
delete object.p
循环判断
1 2 3 4 5 6 7 8 9 10
   | switch(判断语句){      case 条件:        输出;        break;      case 条件:        输出;        break;   default:       输出 }
  | 
 
while 循环
1 2 3 4 5
   | while (expression) {   statement; }
 
 
  | 
 
do-while 循环
1 2 3 4
   | do {   statement; } while (expression);
 
  | 
 
for 循环
1 2 3
   | for (var i = 0; i < 5; i++) {   console.log(i); }
  | 
 
执行步骤如下:
初始化语句: var i=0,只执行第一次
判断语句: i<5
条件改变语句: i++
内部执行: console.log(i)
先执行初始化语句,然后判断条件是否成立,成立就执行内部语句,最后改变条件.重复进行判断.
for-in 循环
1 2 3 4
   | var arr = [1, 2, 3]; for (var key in arr) {   console.log(arr[key]); }
   | 
 
break退出本次循环
continue跳过本次循环执行下次循环
引用类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   |  var a = 1; var b = 2;
  var obj = {   name: "Tom",   sex: "male",   age: 15,   friend: {     name: "Jerry",     age: 10,   }, }; var newobj = {}; 
 
  b = a; console.log(b); 
  var obj2 = obj;
  obj2.name = "Ann"; console.log(obj.name); 
 
  | 
 
1 2 3 4
   | var obj3 = { name: "hello" }; var obj4 = { name: "hello" }; obj3 === obj4; 
 
  | 
 
1 2 3 4 5 6
   | function sum() {   console.log("sum.."); } var sum2 = sum; sum2(); 
 
  | 
 
函数的参数传递
1 2 3 4 5 6 7
   | function inc(n) {   n++; } var a = 10; inc(a); console.log(a); 
 
  | 
 
1 2 3 4 5 6 7
   | function incObj(obj) {   obj.n++; } var o = { n: 10 }; incObj(o); console.log(o); 
 
  | 
 
深拷贝
1 2 3 4 5 6 7 8 9
   | var obj = {   name: "hunger",   age: 3,   friends: ["aa", "bb", "cc"], };
  var obj2 = JSON.parse(JSON.stringify(obj)); obj.age = 4; console.log(obj2.age);
  | 
 
缺点: JSON 不支持函数、引用、undefined、RegExp、Date……
jQuery.extend()
jQuery.extend( [deep], target, object1 [, objectN ] )
其中 deep 为 Boolean 类型,如果是true,则进行深拷贝。
1 2 3 4 5 6 7 8 9 10 11
   | var target = { a: 1, b: 1 }; var copy1 = { a: 2, b: 2, c: { ca: 21, cb: 22, cc: 23 } }; var copy2 = { c: { ca: 31, cb: 32, cd: 34 } }; var result = $.extend(true, target, copy1, copy2);  console.log(target); 
  var target = { a: 1, b: 1 }; var copy1 = { a: 2, b: 2, c: { ca: 21, cb: 22, cc: 23 } }; var copy2 = { c: { ca: 31, cb: 32, cd: 34 } }; var result = $.extend(target, copy1, copy2);  console.log(target); 
  | 
 
递归深拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | function clone(object) {   var object2;   if (!(object instanceof Object)) {     return object;   } else if (object instanceof Array) {     object2 = [];   } else if (object instanceof Object) {     object2 = {};   }      for (let key in object) {     object2[key] = clone(object[key]);   }   return object2; }
  | 
 
函数
函数返回值 return
通过 return 返回结果,调用 return 后,函数立即中断并返回结果,即使后面还有语句也不再执行.
立即执行函数
将整个函数表达式包含在括号内,最后添加一个括号表示执行.
1 2 3
   | (function () {   console.log("hello"); })();
  | 
 
命名冲突
当在同一个作用域内定义了名字相同的变量和方法的话,无论其顺序如何,变量的赋值会覆盖方法的赋值.
1 2 3
   | var fn = 3; function fn() {} console.log(fn); 
   | 
 
当函数执行有命名冲突时,函数执行时载入顺序是变量,函数,参数
1 2 3 4 5 6 7 8
   | function fn(fn) {   console.log(fn);
    var fn = 3;   console.log(fn); }
  fn(10); 
  | 
 
函数作用域(scope)
作用域(scope)指的是变量存在的范围。
在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。
词法作用域
创建当前函数所在的作用域.
递归
一个函数可以指向并调用自身。调用自身的函数我们称之为递归函数。在某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件(避免无限循环或者无限递归)。
作用域链
- 函数在执行的过程中,先从自己内部找变量
 
- 如果找不到,再从创建当前函数所在的作用域去找, 以此往上
 
- 注意找的是变量的当前的状态
 
闭包(closure)
它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
一般情况下使用闭包主要是为了
- 封装数据
 
- 暂存数据
 
面试官想听的版本:
什么是闭包?
由于在 JS 中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数——也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
闭包解决了什么问题?
由于闭包可以缓存上级作用域,那么就使得函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。以平时使用的 Ajax 成功回调为例,这里其实就是个闭包,由于上述的特性,回调就拥有了整个上级作用域的访问和操作能力,提高了极大的便利。开发者不用去写钩子函数来操作上级函数作用域内部的变量了。
闭包的应用?
闭包随处可见,一个 Ajax 请求的成功回调,一个事件绑定的回调方法,一个 setTimeout 的延时回调,或者一个函数内部返回另一个匿名函数,这些都是闭包。简而言之,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都有闭包的身影。
1 2 3 4 5 6 7 8 9 10 11 12 13
   | function car() {   var speed = 0;   function fn() {     speed++;     console.log(speed);   }   return fn; }
  var speedUp = car(); speedUp();  speedUp(); 
 
  | 
 
如果没有这个闭包,函数执行后,里面 speed 变量就会被清理掉。但我们声明了 fn 这个函数,并把它返回出来赋值给新的变量 speedup。因为 speedup 是全局变量,是一直存在的,故这个 fn 函数就一直存在,speed 变量也不会被清理
1 2 3 4 5 6 7 8 9 10
   |  var speedup = (function (speed) {   return function () {     speed++;     console.log(speed);   }; })(3); speedup();  speedup();  speedup(); 
 
  | 
 
经典范例
如下代码输出多少?如果想输出 3,那如何改造代码?
1 2 3 4 5 6 7
   | var fnArr = []; for (var i = 0; i < 10; i++) {   fnArr[i] = function () {     return i;   }; } console.log(fnArr[3]()); 
   | 
 
我的理解: fnArr[i]声明的匿名函数的作用域是全局作用域,先遍历,并未执行,生成闭包(只是 for 循环,并没有执行函数,后面没有小括号),遍历结束开始执行,fnArr 结果是 10 个匿名函数f(),全局作用域下 0-9 的fnArr[i]已经变成 10 了
原理解析:for 循环每次执行,都把 function(){ return i} 这个函数赋值给 fnArr[i],但这个函数不执行。
因为 fnArr[3] =function(){ return i};故当我们调用 fnArr3 时,相当于 function(){ return i};这个函数立刻执行,这时 for 循环已经完成,i 已经变成了 10。故输出 10
如果要输出 3,需要如下改造
1 2 3 4 5 6 7 8 9
   | var fnArr = []; for (var i = 0; i < 10; i++) {   (function (i) {     fnArr[i] = function () {       return i;     };   })(i);  } console.log(fnArr[3]()); 
   | 
 
我的理解: 从 0 开始遍历,(function(0){ fnArr[0] = function(){return i}})(0),
(function(1){ fnArr[1] = function(){return i}})(1)
(function(2){ fnArr[2] = function(){return i}})(2),
(function(3){ fnArr[3] = function(){return i}})(3),
传入 3,最内部的 function 找 i 的变量,一直找到上一层,返回 3.
1 2 3 4 5 6 7 8 9
   | var fnArr = []; for (var i = 0; i < 10; i++) {   fnArr[i] = (function (j) {     return function () {       return j;     };   })(i);  } console.log(fnArr[3]()); 
   | 
 
如下代码输出什么?
1 2 3 4 5 6
   | for (var i = 0; i < 5; i++) {   setTimeout(function () {     console.log(i);   }, 0); }
 
  | 
 
如何连续输出 0,1,2,3,4
1 2 3 4 5 6 7 8
   | for (var i = 0; i < 5; i++) {   (function (j) {     setTimeout(function () {       console.log(j);     }, 0);   })(i); }
 
  | 
 
1 2 3 4 5 6 7 8
   | 
  for (let i = 0; i < 5; i++) {   setTimeout(function () {     console.log(i);   }, 0); }
 
 
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12
   | var an = function (n) {   for (var i = 0; i < 2; i++) {     n++;     console.log(n);      console.log(i);    }   console.log(n);    console.log(i);     };
  var g = an(1);
  | 
 
函数柯里化
只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数.
写一个函数 sum,实现如下调用
1 2 3 4 5 6 7 8 9
   | console.log(sum(1)(2));  console.log(sum(5)(-1)); 
 
  function sum(a) {   return function (b) {     return a + b;   }; }
   | 
 
字符串
1 2
   | str.charAt(0);  str.charCodeAt(0); 
   | 
 
字符串截取
1 2 3 4
   | var str = "hello"; str.substr(1, 3);  str.substring(1, 3);  str.slice(1, 3); 
   | 
 
查找
1 2 3 4
   | str.search("he");  str.indexOf("he");  str.replace("he", "you");  str.match("he"); 
  | 
 
大小写
1 2
   | str.toUpperCase(); str.toLowerCase(); 
   | 
 
去除首尾空格
split()方法使用指定的分隔符字符串将一个 String 对象分割成字符串数组,以将字符串分隔为子字符串,以确定每个拆分的位置。
找到分隔符后,将其从字符串中删除,并将子字符串的数组返回。如果没有找到或者省略了分隔符,则该数组包含一个由整个字符串组成的元素。如果分隔符为空字符串,则将 str 转换为字符数组。如果分隔符出现在字符串的开始或结尾,或两者都分开,分别以空字符串开头,结尾或两者开始和结束。因此,如果字符串仅由一个分隔符实例组成,则该数组由两个空字符串组成。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
   | var str = "The quick brown fox jumps over the lazy dog.";
  var words = str.split(" "); console.log(words[3]);
 
  var chars = str.split(""); console.log(chars[8]);
 
  var strCopy = str.split(); console.log(strCopy);
 
   | 
 
当字符串为空时,split()返回一个包含一个空字符串的数组,而不是一个空数组,如果字符串和分隔符都是空字符串,则返回一个空数组。
1 2 3
   | var str = ""; var strEm = str.split(); console.log(strEm); 
   | 
 
数组
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
   | arr.push(newelement)  arr.pop()  arr.shift()  arr.unshift()  arr.splice(index,替换几个,替换内容) 
  arr.slice(start,end)  arr.join("-")  arr.reverse()  arr.sort(v1-v2) 
  a.concat(array) 
  Array.isArray(obj)  a.indexof(2) 
  a.forEach(fn(element,index,arr){})
  a.map(fn(element){})
 
 
  a.every(fn(element,index,arr){})
  a.some(fn(element,index,arr){})
 
  a.filter(fn(element){})
 
 
  a.reduce(fn(v1,v2),value)
 
  a.abs(x) 
  Array.from(new Set(a)) 
   | 
 
数组扁平化
1 2 3 4 5 6 7 8 9 10
   | const flattenDeep = (arr) =>   Array.isArray(arr)     ? arr.reduce((a, b) => [...a, ...flattenDeep(b)], [])     : [arr];
  flattenDeep([1, [[2], [3, [4]], 5]])[      (1, [2], 3) ].flatMap((v) => v + 1);
 
   | 
 
数组去重
双重循环去重
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | function unique(arr){   for(var i=0; i<arr.length; i++){     for(var j=i+1; j<arr.length; j++>{       if(arr[i]===arr[j]){                  arr.splice(j,1)                  j--                }     })   }   return arr }
  | 
 
new Set 去重
1 2 3 4 5 6 7 8 9
   | function unique(arr) {   return Array.from(new Set(arr)); }
  function unique(arr) {   return [...new Set(arr)]; }
  var unique = (arr) => [...new Set(arr)];
  | 
 
如何将浮点数点左边的数每三位添加一个逗号
如 12000000.11 转化为『12,000,000.11』?
1 2 3 4 5 6 7 8
   | function commafy(num) {   return (     num &&     num.toString().replace(/(\d)(?=(\d{3})+\.)/g, function ($1, $2) {       return $2 + ",";     })   ); }
  | 
 
如何实现数组的随机排序?
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
   | 方法一: var arr = [1,2,3,4,5,6,7,8,9,10]; function randSort1(arr){   for(var i = 0,i < arr.length; i++ ){     var rand = parseInt(Math.random()*len);     var temp = arr[rand];     arr[rand] = arr[i];     arr[i] = temp;   }   return arr; } console.log(randSort1(arr));
  方法二: var arr = [1,2,3,4,5,6,7,8,9,10]; function randSort2(arr){   var mixedArray = [];   while(arr.length > 0){     var randomIndex = parseInt(Math.random()*arr.length);     mixedArray.push(arr[randomIndex]);     arr.splice(randomIndex, 1);   }   return mixedArray; } console.log(randSort2(arr));
  方法三: var arr = [1,2,3,4,5,6,7,8,9,10]; arr.sort(function(){   return Math.random() - 0.5; }) console.log(arr);
   | 
 
Math
四舍五入
1 2
   | Math.round(0.5);  Math.round(-1.5); 
   | 
 
绝对值
最大值
Math.max() 函数返回一组数中的最大值。
参数是value1, value2, ...
是一组数值
1 2 3
   | Math.max(10, 20,30); Math.max.call(null,10, 20,30); Math.max.apply(null,[10, 20,30]);
   | 
 
1 2 3 4
   | floor返回小于参数值的最大整数.向下取整 Math.floor(-3.2)  ceil返回大于参数值的最小参数,向上取整 Math.ceil(-3.2)  
   | 
 
parseInt(值,进制)
1 2 3
   | parseInt(string, radix);
 
 
   | 
 
parseInt()可以将字符串转为数字,也可以将数字取整。向下取整。
如果第一个字符是数字会解析直到遇到非数字结束,如果第一个字符不是数字或者符号(如:+、-)就返回 NaN。
带自动净化功能;带自动截断小数功能,且取整,不四舍五入。
radix: 基数,也就是进制.介于 2-36 之间,超出都是返回 NaN.radix 不写默认是十进制.如果传入字符串是 0x 开头,默认是 16 进制.如果是 0 开头,默认 8 进制.任何数的 0 进制都是它本身.高于进制位能表示的最大值数返回 NaN.比如说 2 的一进制返回 NaN,因为一进制只能用 1 表示,2 作为一进制来表示明显不对.
几次方
1 2
   | Math.pow(x, y);  Math.pow(2, 2); 
   | 
 
平方根
1 2
   | Math.sqrt(4);  Math.sqrt(-4); 
   | 
 
random
Date
1 2 3
   | Date.now();  Date.parse();  Date.parse("2011-01-11"); 
   | 
 
1 2 3 4 5 6 7
   | new Date(); 
 
  var str = "2019-01-11"; new Date(str);
 
 
   | 
 
get 方法
1 2 3 4 5 6 7 8
   | getDate();  getDay();  getFullyear();  getMouth();  getHours();  getMinutes();  getSenconds();  getMilliseconds(); 
   | 
 
set 方法类似于 get 方法,是设置时间
时间格式化过滤器
Vue 版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | {{date | dateFormat }}
  <script>   filters: {          dateFormat: function(date) {              let t = new Date(date);       return (         t.getFullYear() +         "-" +         (t.getMonth() + 1) +         "-" +         t.getDate() +         " " +         t.getHours() +         ":" +         t.getMinutes() +         ":" +         t.getSeconds()       );     }   }, </script>
  | 
 
异步和回调
setTimeout 和 setInterval
区别: setInterval 指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行.
清除定时器: clearTimeout()和 clearInterval()
运行机制
setTimeout 和 setInterval 的运行机制是,将指定的代码移出本次执行,等到下一轮 Event Loop,再检查是否到了指定时间.如果到了,就执行对应代码,不到,就等到下一轮重新判断.这也就意味着,setTimeout 指定的代码,必须等到本次执行的所有代码都执行完,才会执行.
单线程模型
异步的任务放在任务队列(callback queue)中,setTimeout 也是.必须等放在栈(stack)中的同步代码执行完,才会执行任务队列中的.
event loop(事件循环)
经典案例:
1 2 3 4 5
   | for(var i=0; i<5; i++){   setTimeout(fucntion timer(){     comsole.log(i)   },1000) }
  | 
 
我的理解:
开始循环遍历.(同步代码)
i=0.创建一个 console.log(i).放到队列里,并不执行.
i=1.创建一个 console.log(i).放到队列里,并不执行,
直到 i=5.依次创建了 5 个 console.log(i)
因为 setTimeout 是异步,所以必须等同步执行完才开始执行异步.
同步代码 i 循环到 5,输出,有 5 个 console.log(i)在队列里嗷嗷待哺
,把 i=5 赋值进去.得到 5 个 5.
函数节流
在时间内,如果执行,就重新开始
原理:当达到了一定的时间间隔就会执行一次;可以理解为是缩减执行频率
函数节流会用在比 input, keyup 更频繁触发的事件中,如 resize, touchmove, mousemove, scroll。throttle 会强制函数以固定的速率执行。因此这个方法比较适合应用于动画相关的场景。
常用场景:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
 
- 手机号、邮箱验证输入检测
 
- 窗口大小 Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
 
简单版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | function throttle(fn, ms) {   let timer;    return function () {     if (timer) {              return;     }     timer = setTimeout(fn, ms);   }; } function fn() {   console.log("hello"); } var fn2 = throttle(fn, 1000);
  | 
 
时间戳版本:
1 2 3 4 5 6 7 8 9 10
   | const throttle = (fn, wait) => {   let last = 0;   return () => {     const current_time = +new Date();     if (current_time - last > wait) {       fn.apply(this, arguments);       last = +new Date();     }   }; };
  | 
 
函数防抖
原理:将若干函数调用合成为一次,并在给定时间过去之后,或者连续事件完全触发完成之后,调用一次(仅仅只会调用一次)
适合场景:
- 滚动加载,加载更多或滚到底部监听
 
- 谷歌搜索框,搜索联想功能
 
- 高频点击提交,表单重复提交
 
1 2 3 4 5 6 7 8 9 10 11 12
   | function debounce(fn, ms) {   let timer = null;   return () => {     clearTimeout(timer);     timer = setTimerout(fn, ms);   }; }
  function vlog() {   console.log(1); } window.onscroll = debounce(vlog, 500);
  | 
 
带有立即执行选项的防抖函数
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
   |  function now() {   return +new Date(); }
 
 
 
 
 
 
 
  function debounce(func, wait = 50, immediate = true) {   let timer, context, args;
       const later = () =>     setTimeout(() => {              timer = null;                     if (!immediate) {         func.apply(context, args);         context = args = null;       }     }, wait);
       return function (...params) {          if (!timer) {       timer = later();                     if (immediate) {         func.apply(this, params);       } else {         context = this;         args = params;       }                   } else {       clearTimeout(timer);       timer = later();     }   }; }
 
  | 
 
对于按钮防点击来说的实现:
如果函数是立即执行的,就立即调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。一旦你点累了,定时器时间到,定时器重置为 null,就可以再次点击了。
对于延时执行函数来说的实现:
清除定时器 ID,如果是延迟调用就调用函数
节流和防抖的区别
他们的区别在于防抖只会在连续的事件周期结束时执行一次,而节流会在事件周期内按间隔时间有规律的执行多次。
我的理解:
函数节流:
只要又执行函数,就退出.必须等上一次定时器任务执行完.这一段时间只执行一次.
函数防抖:
只要又执行函数,就清除定时器,重新设置定时器.只要重复就重新开始,最后肯定有一次.
DOM 元素创建和选取
DOM 是文档对象模型,是 html 的编程接口
readyState 加载状态
1 2 3 4 5 6 7 8 9 10 11
   | document.location;
  document.location.href;  document.location.protocol;  document.location.hostname;  document.location.port;  document.location.pathname;  document.location.assign("http://www.Google.com");
  document.location.reload(true);  document.location.reload(false); 
   | 
 
1 2 3
   | document.open();  document.close();  document.write(); 
   | 
 
Element表示 html 元素
- element 的属性:
 
- nodeName: 元素标签名
 
- nodeType:元素类型
 
- className:类名
 
- id:元素 id
 
- children:子元素列表
 
- childNodes: 子元素列表(NodeList)
 
- firstChild: 第一个子元素
 
- lastChild:最后一个
 
- nextSibling: 下一个兄弟元素
 
- previousSibling: 上一个兄弟元素
 
- parentNode.parentElement: 父元素
 
查询元素
1 2 3 4 5 6 7 8 9 10 11 12 13
   | document.getElementById("target");
  document.getElementsByClassName("box");
 
  document.querySelector(".box"); document.querySelector("#tatget");
 
 
  document.querySelectorAll("div");
 
 
  | 
 
创建元素
1 2 3 4 5 6 7
   | document.createElement("div");
  document.createTextNode("Hello");
  document.createDocumentFragment();
 
 
  | 
 
修改元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | 在元素末尾添加元素; appendChild(); newDiv.appendChild(newContent);
  在元素之前插入元素; insertBefore();
  替换元素; replaceChild(newElement, oldElement);
  删除元素; parentNode.removeChild(childNode);
  clone元素; node.cloneNode(true);
 
 
   | 
 
DOM 元素属性
1 2 3 4 5 6 7 8 9 10 11 12
   | 获取属性 node.getAttribute("id")
  生成新的属性对象节点 attribute = document.createAttribute(name)
  一般用 setAttribute()设置元素属性 var node = document.getElementById("id") node.setAttribute("id", "newVal")
  删除属性节点 node.removeAttribute("id")
   | 
 
- innerText可写属性,返回元素内包含的文本内容,在多层次的时候按照元素由浅到深的顺序拼接内容
 
1 2 3 4 5 6
   | <div>   <p>     123     <span>456</span>   </p> </div>
   | 
 
外层 div 的 innerText 返回内容是  "123456"
- innerHTML返回元素 html 结构,在写入的时候也会自动构建 DOM
 
样式
修改样式
1
   | document.querySelector("p").style.color = "red";
  | 
 
获取样式 getComputedStyle
1 2 3 4
   | var node = document.querySelector("p"); var color = window.getComputedStyle(node).color; console.log(color);
 
  | 
 
class 操作的增删改查
1 2 3 4 5 6
   | var nodeBox = document.querySelector(".box"); console.log(nodeBox.classList); nodeBox.classList.add("active");  nodeBox.classList.remove("active");  nodeBox.classList.toggle("active");  node.classList.contains("active"); 
  | 
 
页面宽高
1 2 3 4 5 6 7
   | element.clientHeight;  element.offsetHeight;  element.scrollHeight;  element.scrollTop;  element.offsetTop;  window.innerHeight;  window.outerHeight; 
   | 
 
如何判断一个元素是否出现在窗口视野中
滚动的距离 + 窗口的高度 = 目标元素到顶部的距离
1 2 3
   | `window.scrollTop + window.innerHeight = element.offsetTop`;
  $("element").offset().top <= $(window).height() + $(window).scrollTop();
   | 
 
如何判断页面滚动到底部
1 2 3 4 5
   |  $(window).scrollTop() + $(window).height() = $('body').height()
 
  element.scrollTop + element.clientHeight = element.scrollHeight
 
  | 
 
事件
DOM 事件流: 捕获阶段,目标阶段,冒泡阶段
事件绑定
1 2 3 4 5 6 7 8
   | <input id="btnClick" type="button" value="Click here">
  <script type="text/javascript"> var btnClick = document.getElementById("btnClick") btnClick.onclick = function showMessage(){   alert(this.id) } </script>
   | 
 
点击事件是异步的.
会存在覆盖
DOM2 事件处理(升级版)
- addEventListener //绑定事件
 
- removeEventListener //解绑事件
 
所有 DOM 节点都包含这两个方法,且都接受三个函数:
- 事件类型
 
- 事件处理方法
 
- 布尔参数,
true表示在捕获阶段调用,false表示在事件冒泡阶段处理,默认是冒泡阶段 
1 2 3 4 5 6 7
   | btnClick.addEventListener(   "click",   function () {     alert(this.id);   },   false );
   | 
 
不存在覆盖,可以写多个方法
属性方法
1 2 3
   | preventDefault();  stopPropagation();  target(); 
   | 
 
事件代理
事件委托(代理)就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果。
使用事件代理来实现它,监听的元素应该是这些元素的父元素,当我点击父元素内的元素时,父元素都会得到响应,并分发相应的事件。  e.target就是点击的元素。
1 2 3 4 5 6 7
   | $(".container").onclick = function (e) {   console.log(this);   console.log(e.target);   if (e.target.classList.contains("box")) {     console.log(e.target.innerText);   } };
  | 
 
常见事件及自定义事件
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
   | 鼠标事件; click; dblclick;  mouseover;  mouseout;  mouseenter;  mouseleave;  mousedown;  mouseup; 
  触摸事件; touchstart; touchend; touchmove;
  键盘事件; keyup;  keydown;  keypress; 
  页面相关事件; onload;  onmove;  scroll;  resize; 
  表单相关; focus;  blur;  change;  reset;  submit;  input;  onload;  DOMContentLoaded; 
  编辑事件; beforecopy;  beforecut;  beforepaste;  beforeeditfocus;  contextmenu;  losecapture;  select; 
  拖动事件; drag;  dragdrop;  dragend;  dragenter; 
   | 
 
js 拖动 div
- 使用
mouse实现 
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
   | <head> 	<meta charset="UTF-8" /> 	<title> mouse实现div拖动 </title> <style type="text/css"> #div { 	position: absolute; 	left: 100px; 	top: 100px; 	width: 200px; 	height: 200px; 	background-color: #f60; } </style> </head> <body> <div id="drag"></div> <script type="text/javascript"> var drag = document.getElementById("drag"); var dragFlag = false; var x,y;
  drag.onmousedown = function (e) { 	e = e || window.event; 	x = e.clientX - drag.offsetLeft; 	y = e.clientY - drag.offsetTop; 	dragFlag = true; };
  document.onmousemove = function (e) { 	if (dragFlag) { 		e = e || window.event; 		drag.style.left = e.clientX - x + "px"; 		drag.style.top  = e.clientY - y + "px"; 	} };
  document.onmouseup = function (e) { 	dragFlag = false; };
  </script> </body>
   | 
 
- drag 实现
 
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
   | <head> <style> .divblok {     background-color:#ccc;     width:30px;     height:30px;     position:absolute;     left:20px;     top:20px; } </style> </head> <body>    <div id="dragdiv" draggable="true" class="divblok">我要移动 </div> <script> var dragdiv = document.querySelector('#dragdiv'); var x, y;  
  dragdiv.addEventListener('dragstart', function (e) {     e.dataTransfer.effectAllowed = "move";       e.dataTransfer.setData("text", '');       x = e.offsetX || e.layerX;     y = e.offsetY || e.layerY;     return true; }, false);
  document.addEventListener('dragover', function (e) {     e.preventDefault()|| e.stopPropagation(); }, false);
  document.addEventListener('drop', function (e) {     dragdiv.style.left = (e.pageX - x) + 'px';     dragdiv.style.top = (e.pageY - y) + 'px';     e.preventDefault() || e.stopPropagation();   }, false); </script> </body>
   | 
 
自定义事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | var EventCenter = {   on: function (type, handler) {     document.addEventListener(type, handler);   },   fire: function (type, data) {     return document.dispatchEvent(       new CustomEvent(type, {         detail: data,       })     );   }, }; EventCenter.on("hello", function (e) {   console.log(e.detail); }); EventCenter.fire("hello", "你好");
 
  | 
 
JS 动画
requestAnimationFrame
可以在一定时间内自动执行
1 2 3 4 5 6 7 8 9
   | function move() {   if (moveOffset < offsetX) {     ball.style.left = parseInt(getComputedStyle(ball).left) + step + "px";     moveOffset += step;     requestAnimationFrame(move);   } }
  move();
  | 
 
BOM
bom 指浏览器对象模型,核心是 window 对象。是浏览器的实例
window.innerHeight属性,window.innerWidth属性.这两个属性返回网页的 CSS 布局占据的浏览器窗口的高度和宽度,单位为像素。很显然,当用户放大网页的时候(比如将网页从 100%的大小放大为 200%),这两个属性会变小。
注意,这两个属性值包括滚动条的高度和宽度。
- scrollX:滚动条横向偏移
 
- scrollY:滚动条纵向偏移
 
这两个值随着滚动位置变化而变化
window.frames返回一个类似数组的对象,成员为页面内的所有框架,包括 frame 元素和 iframe 元素。
需要注意的是,window.frames 的每个成员对应的是框架内的窗口(即框架的 window 对象),获取每个框架的 DOM 树,需要使用window.frames[0].document。
1 2
   | var iframe = window.getElementsByTagName("iframe")[0]; var iframe_title = iframe.contentWindow.title;
  | 
 
上面代码用于获取框架页面的标题。
iframe 元素遵守同源政策,只有当父页面与框架页面来自同一个域名,两者之间才可以用脚本通信,否则只有使用 window.postMessage 方法。
在 iframe 框架内部,使用 window.parent 指向父页面。
navigator
指向一个包含浏览器相关信息的对象.
window.getComputedStyle
getComputedStyle 是一个可以获取当前元素所有最终使用的 CSS 属性值
1
   | var style = window.getComputedStyle("元素", "伪类")
  | 
 
URL 的编码/解码方法
JavaScript 提供四个 URL 的编码/解码方法。
- decodeURI()
 
- decodeURIComponent()
 
- encodeURI()
 
- encodeURIComponent()
 
区别
encodeURI 方法不会对下列字符编码
1 2 3
   | 1. ASCII字母 2. 数字 3. ~!@#$&*()=:/,;?+'
   | 
 
encodeURIComponent 方法不会对下列字符编码
1 2 3
   | 1. ASCII字母 2. 数字 3. ~!*()'
   | 
 
alert(),prompt(),confirm()
alert()、prompt()、confirm()都是浏览器用来与用户互动的方法。它们会弹出不同的对话框,要求用户做出回应。
需要注意的是,alert()、prompt()、confirm()这三个方法弹出的对话框,都是浏览器统一规定的式样,是无法定制的。