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()这三个方法弹出的对话框,都是浏览器统一规定的式样,是无法定制的。