如果js是多线程的,在运行时多个线程同时对DOM元素进行操作,那具体以哪个线程为主就是个问题了
HTML5新的标准中允许使用new Worker的方式来开启一个新的线程,去运行一段单独的js文件脚本,但是在这个新线程中严格的要求了可以使用的功能,比如说他只能使用ECMAScript, 不能访问DOM和BOM。这也就限制死了多个线程同时操作DOM元素的可能
Number、String、Boolean、Null、undefined、object、symbol(ES6),bigInt(ES11).
原始数据类型: - 布尔类型:布尔表示一个逻辑实体,可以有两个值:true 和 false。 - Null 类型:Null 类型只有一个值: null。 - Undefined 类型:一个没有被赋值的变量会有个默认值 undefined。 - 数字类型:根据 ECMAScript 标准,JavaScript 中只有一种数字类型:基于 IEEE 754 标准的双精度 64 位二进制格式的值(-(253 -1) 到 253 -1)。它并没有为整数给出一种特定的类型。除了能够表示浮点数外,还有一些带符号的值:+Infinity,-Infinity 和 NaN (非数值,Not-a-Number)。 - BigInt 类型:BigInt类型是 JavaScript 中的一个基础的数值类型,可以用任意精度表示整数。使用 BigInt,您可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。BigInt是通过在整数末尾附加 n 或调用构造函数来创建的。 - String字符串类型:JavaScript的字符串类型用于表示文本数据。它是一组16位的无符号整数值的“元素”。在字符串中的每个元素占据了字符串的位置。第一个元素的索引为0,下一个是索引1,依此类推。字符串的长度是它的元素的数量。 - Symbols符号类型:符号(Symbols)是ECMAScript 第6版新定义的。符号类型是唯一的并且是不可修改的, 并且也可以用来作为Object的key的值(如下). 在某些语言当中也有类似的原子类型(Atoms). 你也可以认为为它们是C里面的枚举类型。 引用数据类型: - Object对象: 在计算机科学中, 对象是指内存中的可以被标识符引用的一块区域。
栈和对的区别
栈的特性:先进后出,主要为一个线程独享,为这个线程的函数的调用服务的。用于存放返回地址,临时变量而用。栈的内存一般都是由编译器自己来分配释放的,编译器所分配的内存是连续的,当定义一个变量的时候,在当前栈区的尾部来分配心的变量的内存。在windows系统里面栈的大小是2M,在linux系统里面栈的大小是8M,可以使用ulimit-s来设置栈的大小。栈的空间的分配是由高地址向低地址分配的。
堆的分配和释放是由程序员来分配和释放。在windows系统里面一般是小于2G的。因为系统是用链表来实现空闲地址空间的,所以堆的内存空间不是连续的,链表的遍历也是由低地址到高地址的,所以分配内存也是又低地址向高地址分配。
https://blog.csdn.net/choudan8888/article/details/88538704
Object包含了哪几种类型?
其中包含了Date、function、Array等。这三种是常规用的。
JS中typeof 输出分别是什么
{ } 、[ ] 输出 object。
console.log 输出 function。
console.log() 输出undefined
Number(‘as’) == NaN?
虽然 Number(‘as’) 输出 NaN。
但是这的输出是false
因为js规定NaN不等于任何值 包括NaN
需要用isNaN() 去判断是不是NaN
null 和undefined 有什么区别
Null 只有一个值,是 null。不存在的对象。
Undefined 只有一个值,是undefined。没有初始化。undefined 是从 null 中派生出来的。
简单理解就是:undefined 是没有定义的,null 是定义了但是为空。
null不存在的原因是什么?如何解决?
不存在的原因
解决方法: 做判断处理的时候,放在设定值的最前面
typeof
typeof null // object typeof undefined // undefined typeof [] //object typeof console.log // function typeof console.log() //undefined typeof 1 // number typeof "1" //string
instanceof
原理 因为A instanceof B 可以判断A是不是B的实例,返回一个布尔值,由构造类型判断出数据类型
console.log(arr instanceof Array ); // true console.log(date instanceof Date ); // true console.log(fn instanceof Function ); // true //注意: instanceof 后面一定要是对象类型,大小写不能写错,该方法试用一些条件选择或分支
.通过Object下的toString.call()方法来判断
Object.prototype.toString.call(); console.log(toString.call(123)); //[object Number] console.log(toString.call('123')); //[object String] console.log(toString.call(undefined)); //[object Undefined] console.log(toString.call(true)); //[object Boolean] console.log(toString.call({})); //[object Object] console.log(toString.call([])); //[object Array] console.log(toString.call(function(){})); //[object Function]
根绝对象的constructor判断
console.log('数据类型判断' - constructor); console.log(arr.constructor === Array); //true console.log(date.constructor === Date); //true console.log(fn.constructor === Function); //true
jQuery的方法 $.isArray()等…
判断数据类型的方式
typeof 不可以用来判断数组
typeof 数组 返回object
===
‘array’ 或toString.call(arr) ===
‘[object Array]’arr.__proto__
=== Array.prototype 和instanceof原理上是一样的==
先转换类型再比较,===
先判断类型,如果不是同一类型直接为false。
===
表示恒等于,比较的两边要绝对的相同
alert(0 == ""); // true alert(0 == false); // true alert("" == false); // true alert(0 === ""); // false alert(0 === false); // false alert("" === false); // false
先说 ===
严格等于,这个比较简单,具体比较规则如下:
1、如果类型不同,就[不相等]
2、如果两个都是数值,并且是同一个值,那么[相等];(!例外)的是,如果其中至少一个是NaN,那么[不相等]。(判断一个值是否是NaN,只能用isNaN()来判断)
3、如果两个都是字符串,每个位置的字符都一样,那么[相等];否则[不相等]。
4、如果两个值都是true,或者都是false,那么[相等]。
5、如果两个值都引用同一个对象或函数,那么[相等];否则[不相等]。
6、如果两个值都是null,或者都是undefined,那么[相等]。
再说 ==,具体比较规则如下:
1、如果两个值类型相同,进行 === 比较,比较规则同上
2、如果两个值类型不同,他们可能相等。根据下面规则进行类型转换再比较:
a、如果一个是null、一个是undefined,那么[相等]。
b、如果一个是字符串,一个是数值,把字符串转换成数值再进行比较。
c、如果任一值是 true,把它转换成 1 再比较;如果任一值是 false,把它转换成 0 再比较。
d、如果一个是对象,另一个是数值或字符串,把对象转换成基础类型的值再比较。对象转换成基础类型,利用它的toString或者valueOf方法。js核心内置类,会尝试valueOf先于toString;例外的是Date,Date利用的是toString转换。非js核心的对象,令说(比较麻烦,我也不大懂)
e、任何其他组合(array数组等),都[不相等]。
双等号比较规则
如果类型不相同则进行类型转换
在进行比较时,该运算符还遵循以下规则
if语句判断规则
一个值为 true 或者 false 的表达式。如果需要,非 Boolean 表达式也可以被转换为 Boolean 值,但是要遵循下列规则:
所有的对象都被当作 true。
当且仅当字符串为空时,该字符串被当作 false。
null 和 undefined 被当作 false。
当且仅当数字为零时,该数字被当作 false。
null表示没有对象,即该处不应该有值
作为函数的参数,表示该函数的参数不是对象
作为对象原型链的终点
undefined表示缺少值,即此处应该有值,但没有定义
定义了形参,没有传实参,显示undefined
对象属性名不存在时,显示undefined
函数没有写返回值,即没有写return,拿到的是undefined
写了return,但没有赋值,拿到的是undefined
null和undefined转换成number数据类型
null默认转为0
undefined 默认转为 NaN
绝对值小于 2的53次方
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3T7QzQm9-1641822732979)(image/20190311194017886.png)]
__proto__
和 constructorhttps://blog.csdn.net/cc18868876837/article/details/81211729 这篇文章讲述的很清除了
所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法。
所有 JavaScript 中的对象都是位于JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。原型链顶端的 Object 的实例。
a instanceof A
instanceof 就是可以用来判断一个变量是否是属于某个对象的实例,instanceof检测的是这个变量的原型属于谁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZNF7V5J-1641822732980)(image/20210107170912701.jpg)]
instanceof 操作符其实就是检查左侧的元素的__proto__
链上有没有右侧类或对象的prototype存在。
同理 当某某某是某某某的实例时 其实也是证明左侧的__proto__
链上有右侧类或对象的prototype存在。
instanceof 的局限性
(1)instanceof无法直接判断原始数据类型
var str1 = 'hello word'; var str2 = new String('hello world'); console.log(str1 instanceof String); //false console.log(str2 instanceof String); //true
我们可以这样来解决:
class PrimitiveString{ static [Symbol.hasInstance](x){ return typeof x === 'string' } } console.log('hello world' instanceof PrimitiveString); //true
Symbol.hasInstance 可以理解为一个自定义 instanceof 行为的方法,上面代码的功能等于typeof (‘hello world’ === ‘string’)。
(2)instanceof 对于特殊类型无法判断
console.log(new Date() instanceof Date); //true console.log(new Date() instanceof Object); //true console.log([] instanceof Array); //true console.log([] instanceof Object); //true
instanceof只能用来判断两个对象是否属于实例关系,而不能判断一个是里究竟属于哪种类型。
function F(){}; var o = {}; typeof F; //==> function typeof o; //==> object typeof F.prototype; //==> object typeof o.prototype; //==> undefinded typeof new F; //==> object typeof (new F).prototype; //==> undefined typeof (new F).__proto__; //==> object typeof F.__proto__; //==> function typeof o.__proto__; //==> object typeof Object; //==> function typeof Function; //==> function typeof (new Function).prototype; //==> object typeof (new Function).__proto__; //==> function typeof (new Object).prototype; //==> undefined typeof (new Object).__proto__; //==> object typeof Object.prototype; //==> object typeof Object.__proto__; //==> function typeof Function.prototype; //==> function typeof Function.__proto__; //==> function
原文链接
console.log(typeof [].__proto__) console.log([].__proto__.__proto__) console.log([].__proto__.__proto__.__proto__) Object(0) [] [Object: null prototype] {} null 所以空数组的原型链 首先指向 Array.prototype 然后指向 Object.prototype 然后指向null
js 是单线程的.
Promise是什么
Promise
new Promise( function (resolve, reject) { // 一段耗时的异步操作 resolve('成功') // 数据处理完成 // reject('失败') // 数据处理出错 } ).then( (res) => {console.log(res)}, // 成功 (err) => {console.log(err)} // 失败 )
结论:promise作为队列最为重要的特性,我们在任何一个地方生成了一个promise队列之后,我们可以把他作为一个变量传递到其他地方。
假如在.then()的函数里面不返回新的promise,会怎样?
then()
1、接收两个函数作为参数,分别代表fulfilled(成功)和rejected(失败)
2、.then()返回一个新的Promise实例,所以它可以链式调用
3、当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行
4、状态响应函数可以返回新的promise,或其他值,不返回值也可以我们可以认为它返回了一个null;
5、如果返回新的promise,那么下一级.then()会在新的promise状态改变之后执行
6、如果返回其他任何值,则会立即执行下一级.then()
常见用法:
异步操作和定时器放在一起,,如果定时器先触发,就认为超时,告知用户;
例如我们要从远程的服务家在资源如果5000ms还没有加载过来我们就告知用户加载失败
现实中的用法
回调包装成Promise,他有两个显而易见的好处:
1、可读性好
2、返回 的结果可以加入任何Promise队列
https://www.jianshu.com/p/1b63a13c2701
递归实现
function diyPromiseAll(arr) { // 这个是保存返回值的数组 let res_arr = []; // 这个是当前遍历到的promise的下标 let currentPromiseIndx = 0; return new Promise((resolve, reject) => { // 这里差不多是个递归函数,如果没有遍历到最后一个promise,那么将一致递归到最后一个为止 function dealPromise(){ if (currentPromiseIndx === arr.length - 1) { arr[currentPromiseIndx].then(res => { res_arr.push(res); resolve(res_arr); }) } else { arr[currentPromiseIndx].then(res => { res_arr.push(res); currentPromiseIndx++; dealPromise(); }) } } dealPromise(); }) }
async和awati对 reduce和map的影响
https://blog.csdn.net/weixin_33725239/article/details/91390618
宏任务和微任务
题目:
思路
let int = 1; setTimeout(function() { console.log(int) // 5 第5个输出 int = 2 new Promise((resolve, reject) => { resolve() }).then(function() { console.log(int) // 2 第7个输出 int = 7 }) console.log(int) // 2 第6个输出 }) int = 3 console.log(int) // 3 第1个输出 new Promise((resolve, reject) => { console.log(int) // 3 第2个输出 return resolve(int = 4) }).then(function(res) { console.log(int) // 4 第4个输出 int = 5 setTimeout(function() { console.log(int) // 7 第8个输出 int = 8 }) return false }) console.log(int) // 4 第3个输出
var a = 100; console.log(a, window.a); // 100 100 let b = 10; console.log(b, window.b); // 10 undefined const c = 1; console.log(c, window.c); // 1 undefined
console.log(a); // undefined 变量提升,已声明未赋值 var a = 100; console.log(b); // 报错 Uncaught ReferenceError: Cannot access 'a' before initialization let b = 10; console.log(c); // 报错 Uncaught ReferenceError: Cannot access 'a' before initialization const c = 1;
if(true) { var a = 100; let b = 200; } console.log(a); // 100 console.log(b); // 报错 Uncaught ReferenceError: b is not defined
var a = 20; var a = 30; console.log(a); // 30 let b = 10; let b = 5; console.log(b); // 报错 Uncaught SyntaxError: Identifier 'b' has already been declared
var a = 10; if(true) { console.log(a); // 10 var a = 50; } if(true) { console.log(a); // 报错 Uncaught ReferenceError: Cannot access 'a' before initialization let a = 500; }
属性
1.concat() 连接两个或者更多的数组,并返回结果
concat() 方法用于连接两个或多个数组。
该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2epE6DmO-1641822732981)(image/image-20210331234635891.png)]
2.every 检测数值元素的每个元素是否都符合条件
every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。
every() 方法使用指定函数检测数组中的所有元素:
every() 不会改变原始数组。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z0EQF6WX-1641822732983)(image/image-20210331234855910.png)]
3.fill
使用固定值填充数组: 改变原数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gm1zhGjY-1641822732984)(image/image-20210331234930357.png)]
4.filter
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
5.find
6.findIndex
7.forEach()
8.from
9.includes
10.indexOf
11.isArray
12.join
13.map
14.reduce
15.reduceRight
16.push
17.pop
18.reverse
19.slice
20.some
21.splice
22.unshift
23.shift
24.valueOf
https://www.runoob.com/jsref/jsref-obj-array.html
添加元素类:(返回新的长度)
删除元素类:(返回的是被删除的元素)
颠倒顺序:
插入、删除、替换数组元素:(返回被删除的数组)
排序
map() 方法:原数组中的每个元素调用一个指定方法后,返回返回值组成的新数组。
array.map(function(currentValue,index,arr), thisValue)
例子:
有一个数组x=[1, 2, 3, 4, 5, 6, 7, 8, 9],求x^2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qgr4LBU2-1641822732985)(image/image-20210319175300515.png)]
function pow(x){ //定义一个平方函数 return x*x; } var arr=[1,2,3,4,5,6,7,8,9]; var result = arr.map(pow); //map()传入的是函数对象本身 console.log(result); //结果:[1,4,9,16,25,36,49,64,81];
reduce()方法: 为数组中的每一个元素依次执行回调函数(不包括数组中被删除或从未被赋值的元素),返回一个具体的结果。
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
例子1:数组求和
var arr = [1, 2, 3, 4, 5]; sum = arr.reduce(function(prev, cur, index, arr) { console.log(prev, cur, index); //输出的是第一项的值或上一次叠加的结果,正在被处理的元素,正在被处理的元素的索引值 return prev + cur; }) console.log(arr, sum); //输入数组本身和最后的结果 // 简写 var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { //callback函数只传入了previousValue、currentValue两个参数 return x + y; }); // 结果:25,initialValue默认为0 // 传入一个初始值 var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; },10); // 结果:35,变成了初始值和数组的和
例子2:
不要使用JavaScript内置的parseInt()函数,利用map和reduce操作实现一个string2int()函数
思路:1.先把字符串13579先变成Array——[1, 3, 5, 7, 9]
2.再利用reduce()就可以写出一个把字符串转换为Number的函数。
function string2int(s) { var arr = s.split('').map(function(x){ return +x; }) return arr.reduce(function(prev,res){ return prev*10+res; }) } // 测试: if (string2int('0') === 0 && string2int('12345') === 12345 && string2int('12300') === 12300) { if (string2int.toString().indexOf('parseInt') !== -1) { console.log('请勿使用parseInt()!'); } else if (string2int.toString().indexOf('Number') !== -1) { console.log('请勿使用Number()!'); } else { console.log('测试通过!'); } } else { console.log('测试失败!'); }
tips:
字符串前面加+号可快速转为number类型
四种字符串转number的方法
例子3 求一串字符串中每个字母出现的次数
var arrString = 'abcdaabc'; arrString.split('').reduce(function(res, cur) { res[cur] ? res[cur] ++ : res[cur] = 1 return res; }, {})
例子4
请把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。
输入:[‘adam’, ‘LISA’, ‘barT’],输出:[‘Adam’, ‘Lisa’, ‘Bart’]。
function normalize(arr) { return arr.map(function(x){ x=x.toLowerCase(); //注意将x转化成小写之后要重新赋值给x,否则x没有变化 x=x[0].toUpperCase()+ x.substr(1); //substr(start,length)字符串分割,从start开始,截取length长 return x; }) } // 测试: if (normalize(['adam', 'LISA', 'barT']).toString() === ['Adam', 'Lisa', 'Bart'].toString()) { console.log('测试通过!'); } else { console.log('测试失败!'); }
filter
filter也是一个常用的操作,它用于把Array
的某些元素过滤掉,然后返回剩下的元素。
和map()
类似,Array
的filter()
也接收一个函数。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是true
还是false
决定保留还是丢弃该元素。
例子1: 选出偶数
var arr = [1, 2, 4, 5, 6, 9, 10, 15]; var r = arr.filter(function (x) { return x % 2 === 0; }); r; // [1, 5, 9, 15]
回调函数
filter()
接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array
的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:
var arr = ['A', 'B', 'C']; var r = arr.filter(function (element, index, self) { console.log(element); // 依次打印'A', 'B', 'C' console.log(index); // 依次打印0, 1, 2 console.log(self); // self就是变量arr return true; });
利用filter
,可以巧妙地去除Array
的重复元素:
var arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry']; var r = arr.filter(function (element, index, self) { return self.indexOf(element) === index; }); console.log(r);
去除重复元素依靠的是indexOf
总是返回第一个元素的位置,后续的重复元素位置与indexOf
返回的位置不相等,因此被filter
滤掉了。
sort
JavaScript的Array
的sort()
方法就是用于排序的,但是排序结果可能让你大吃一惊:
// 看上去正常的结果: ['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft']; // apple排在了最后: ['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple'] // 无法理解的结果: [10, 20, 1, 2].sort(); // [1, 10, 2, 20]
第二个排序把apple
排在了最后,是因为字符串根据ASCII码进行排序,而小写字母a
的ASCII码在大写字母之后。
第三个排序结果是什么鬼?简单的数字排序都能错?
这是因为Array
的sort()
方法默认把所有元素先转换为String再排序,结果'10'
排在了'2'
的前面,因为字符'1'
比字符'2'
的ASCII码小。
如果不知道sort()
方法的默认排序规则,直接对数字排序,绝对栽进坑里!
幸运的是,sort()
方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。
要按数字大小排序,我们可以这么写:
'use strict'; var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; }); console.log(arr); // [1, 2, 10, 20] // 倒序 var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return 1; } if (x > y) { return -1; } return 0; }); // [20, 10, 2, 1] //number类型排序重新写 console.log(arr.sort((x, y) => x - y)); // [1, 2, 10, 20]
最后,sort()
方法会直接对Array
进行修改,它返回的结果仍是当前Array
:
var a1 = ['B', 'A', 'C']; var a2 = a1.sort(); a1; // ['A', 'B', 'C'] a2; // ['A', 'B', 'C'] a1 === a2; // true, a1和a2是同一对象
对于数组,除了map()
、reduce
、filter()
、sort()
这些方法可以传入一个函数外,Array
对象还提供了很多非常实用的高阶函数。
every
every()
方法可以判断数组的所有元素是否满足测试条件。
var arr = ['Apple', 'pear', 'orange']; console.log(arr.every(function (s) { return s.length > 0; })); // true, 因为每个元素都满足s.length>0 console.log(arr.every(function (s) { return s.toLowerCase() === s; })); // false, 因为不是每个元素都全部是小写
find
find()
方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined
:
var arr = ['Apple', 'pear', 'orange']; console.log(arr.find(function (s) { return s.toLowerCase() === s; })); // 'pear', 因为pear全部是小写 console.log(arr.find(function (s) { return s.toUpperCase() === s; })); // undefined, 因为没有全部是大写的元素
findIndex
findIndex()
和find()
类似,也是查找符合条件的第一个元素,不同之处在于findIndex()
会返回这个元素的索引,如果没有找到,返回-1
:
var arr = ['Apple', 'pear', 'orange']; console.log(arr.findIndex(function (s) { return s.toLowerCase() === s; })); // 1, 因为'pear'的索引是1 console.log(arr.findIndex(function (s) { return s.toUpperCase() === s; })); // -1
forEach
forEach()
和map()
类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()
常用于遍历数组,因此,传入的函数不需要返回值:
var arr = ['Apple', 'pear', 'orange']; arr.forEach(console.log); // 依次打印每个元素
其他
JavaScript中findIndex和indexOf的区别
indexOf 的第一个参数 expect a value,可以用于原始类型的数组.
丢进去的是要找的元素,直接找元素。
findIndex 的一个参数 expect a callback,可以用于复杂数据类型的数组或者查找条件比一个值要复杂的情况.
丢进去的是一个函数,找满足函数关系的元素。
apply()把参数打包成Array再传入; call()把参数按顺序传入。 Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5
利用apply()
,我们还可以动态改变函数的行为。
var count = 0; var oldParseInt = parseInt; // 保存原函数 window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // 调用原函数 }; // 测试: parseInt('10'); parseInt('20'); parseInt('30'); console.log('count = ' + count); // 3
模拟call实现
思路:
给object对象添加方法fn 运行object.fn时this便指向了object,从而实现了改变this指向
通过arguments获取函数参数
var a = 100 let object = { a: 1, b: 2 } function foo (n1, n2) { console.log(n1) console.log(n2) console.log(this.a) } Function.prototype.call1 = function (o) { let object = o || window object.fn = this // 获取参数 let params = [] for (let i = 1; i < arguments.length; i++) { params.push(arguments[i]) // params.push('arguments[' + i + ']') } object.fn(...params) // 运行函数 // eval('object.fn(' + params +')') } foo.call1(object,'lisi', 'zhangsan')
JavaScript还有一个免费赠送的关键字arguments
,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments
类似Array
但它不是一个Array
:
function foo(x) { console.log('x = ' + x); // 10 for (var i=0; i<arguments.length; i++) { console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30 } } foo(10, 20, 30);
arguments
可以拿到调用者传入的所有参数
function foo(a, b, ...rest) { console.log('a = ' + a); console.log('b = ' + b); console.log(rest); } foo(1, 2, 3, 4, 5); // 结果: // a = 1 // b = 2 // Array [ 3, 4, 5 ] foo(1); // 结果: // a = 1 // b = undefined // Array []
rest参数只能写在最后,前面用...
标识,从运行结果可知,传入的参数先绑定a
、b
,多余的参数以数组形式交给变量rest
,所以,不再需要arguments
我们就获取了全部参数。
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:
function foo() { var x = 'Hello, ' + y; console.log(x); var y = 'Bob'; } foo(); // 提升后 function foo() { var y; // 提升变量y的申明,此时y为undefined var x = 'Hello, ' + y; console.log(x); y = 'Bob'; }
全局变量会绑定到window
上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量MYAPP: var MYAPP = {}; // 其他变量: MYAPP.name = 'myapp'; MYAPP.version = 1.0; // 其他函数: MYAPP.foo = function () { return 'foo'; };
jQuery 就是这么实现的
var [x, y, z] = ['hello', 'JavaScript', 'ES6']; // x, y, z分别被赋值为数组对应元素: console.log('x = ' + x + ', y = ' + y + ', z = ' + z);
解构赋值可以忽略某些元素
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素 z; // 'ES6'
对象解构赋值可以快速获取对象指定的属性
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school' }; var {name, school,age, passport} = person; // name, age, passport分别被赋值为对应属性: console.log('name = ' + name + ', age = ' + age + ', passport = ' + passport+ ',school='+ school);
可以对嵌套对象属性进行赋值,要保证对应的层次是一致的:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school', address: { city: 'Beijing', street: 'No.1 Road', zipcode: '100001' } }; var {name, address: {city, zip}} = person; name; // '小明' city; // 'Beijing' zip; // undefined, 因为属性名是zipcode而不是zip // 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性: address; // Uncaught ReferenceError: address is not defined
解构赋值还可以使用默认值, 避免不存在属性返回undefined
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678' }; // 如果person对象没有single属性,默认赋值为true: var {name, single=true} = person; name; // '小明' single; // true
如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如,下面的函数可以快速创建一个Date
对象:
function buildDate({year, month, day, hour=0, minute=0, second=0}) { return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second); }
它的方便之处在于传入的对象只需要year
、month
和day
这三个属性:
buildDate({ year: 2017, month: 1, day: 1 }); // Sun Jan 01 2017 00:00:00 GMT+0800 (CST)
也可以传入hour
、minute
和second
属性:
buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 }); // Sun Jan 01 2017 20:15:00 GMT+0800 (CST)
ES6 新增的一种函数
x => x*x 等价于 function (x) { return x*x; }
箭头函数就相当于是匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }
和return
都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }
和return
:
x => { if (x > 0) { return x * x; } else { return - x * x; } }
如果参数不是一个,就需要用括号()
括起来:
/ 两个参数: (x, y) => x * x + y * y // 无参数: () => 3.14 // 可变参数: (x, y, ...rest) => { var i, sum = x + y; for (i=0; i<rest.length; i++) { sum += rest[i]; } return sum; }
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this
是词法作用域,由上下文确定。
回顾前面的例子,由于JavaScript函数对this
绑定的错误处理,下面的例子无法得到预期结果:
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = function () { return new Date().getFullYear() - this.birth; // this指向window或undefined }; return fn(); } };
现在,箭头函数完全修复了this
的指向,this
总是指向词法作用域,也就是外层调用者obj
:
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象 return fn(); } }; obj.getAge(); // 25
如果使用箭头函数,以前的那种hack写法:
var that = this;
就不再需要了
由于this
在箭头函数中已经按照词法作用域绑定了,所以,用call()
或者apply()
调用箭头函数时,无法对this
进行绑定,即传入的第一个参数被忽略:
var obj = { birth: 1990, getAge: function (year) { var b = this.birth; // 1990 var fn = (y) => y - this.birth; // this.birth仍是1990 return fn.call({birth:2000}, year); } }; obj.getAge(2015); // 25
这里的生成器和python的差不多
unction foo(x) { return x + x; } var r = foo(1); // 调用foo函数
函数在执行过程中,如果没有遇到return
语句(函数末尾如果没有return
,就是隐含的return undefined;
),控制权无法交回被调用的代码。
generator跟函数很像,定义如下:
function* foo(x) { yield x + 1; yield x + 2; return x + 3; }
generator和函数不同的是,generator由function*
定义(注意多出的*
号),并且,除了return
语句,还可以用yield
返回多次。
function* fib(max) { var t, a = 0, b = 1, n = 0; while (n < max) { yield a; [a, b] = [b, a + b]; n ++; } return; } fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
直接调用一个generator和调用函数不一样,fib(5)
仅仅是创建了一个generator对象,还没有去执行它
调用generator对象有两个方法,一是不断地调用generator对象的next()
方法:
var f = fib(5); f.next(); // {value: 0, done: false} f.next(); // {value: 1, done: false} f.next(); // {value: 1, done: false} f.next(); // {value: 2, done: false} f.next(); // {value: 3, done: false} f.next(); // {value: undefined, done: true}
next()
方法会执行generator的代码,然后,每次遇到yield x;
就返回一个对象{value: x, done: true/false}
,然后“暂停”。返回的value
就是yield
的返回值,done
表示这个generator是否已经执行结束了。如果done
为true
,则value
就是return
的返回值。
当执行到done
为true
时,这个generator对象就已经全部执行完毕,不要再继续调用next()
了。
第二个方法是直接用for ... of
循环迭代generator对象,这种方式不需要我们自己判断done
:
function* fib(max) { var t, a = 0, b = 1, n = 0; while (n < max) { yield a; [a, b] = [b, a + b]; n ++; } return; } for (var x of fib(10)) { console.log(x); // 依次输出0, 1, 1, 2, 3, ... }
generator和普通函数相比,有什么用?
因为generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个generator就可以实现需要用面向对象才能实现的功能。例如,用一个对象来保存状态,得这么写
var fib = { a: 0, b: 1, n: 0, max: 5, next: function () { var r = this.a, t = this.a + this.b; this.a = this.b; this.b = t; if (this.n < this.max) { this.n ++; return r; } else { return undefined; } } };
用对象的属性来保存状态,相当繁琐。
generator还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。这个好处要等到后面学了AJAX以后才能体会到。
没有generator之前的黑暗时代,用AJAX时需要这么写代码:
ajax('http://url-1', data1, function (err, result) { if (err) { return handle(err); } ajax('http://url-2', data2, function (err, result) { if (err) { return handle(err); } ajax('http://url-3', data3, function (err, result) { if (err) { return handle(err); } return success(result); }); }); });
回调越多,代码越难看。
有了generator的美好时代,用AJAX时可以这么写:
try { r1 = yield ajax('http://url-1', data1); r2 = yield ajax('http://url-2', data2); r3 = yield ajax('http://url-3', data3); success(r3); } catch (err) { handle(err); }
只要在某个内部作用域内访问在当前作用域之外定义的变量,就会创建闭包。 它允许你从内部函数访问外部函数的作用域。 在JS中,每次创建函数时都会创建闭包。 要使用闭包,只需在另一个函数内定义一个函数并暴露它。
闭包:是指有权访问另一个函数作用域中的变量的函数。
函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个对Array
的求和。通常情况下,求和的函数是这样定义的:
function sum(arr) { return arr.reduce(function (x, y) { return x + y; }); } sum([1, 2, 3, 4, 5]); // 15
但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!
function lazy_sum(arr) { var sum = function () { return arr.reduce(function (x, y) { return x + y; }); } return sum; }
当我们调用lazy_sum()
时,返回的并不是求和结果,而是求和函数:
var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
调用函数f
时,才真正计算求和的结果:
f(); // 15
在这个例子中,我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
请再注意一点,当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使传入相同的参数:
var f1 = lazy_sum([1, 2, 3, 4, 5]); var f2 = lazy_sum([1, 2, 3, 4, 5]); f1 === f2; // false
f1()
和f2()
的调用结果互不影响。
闭包
注意到返回的函数在其定义内部引用了局部变量arr
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()
才执行。我们来看一个例子:
function count() { var arr = []; for (var i=1; i<=3; i++) { // 使用let就好了啊 arr.push(function () { return i * i; }); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2];
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array
中返回了。
你可能认为调用f1()
,f2()
和f3()
结果应该是1
,4
,9
,但实际结果是:
f1(); // 16 f2(); // 16 f3(); // 16
全部都是16
!原因就在于返回的函数引用了变量i
,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i
已经变成了4
,因此最终结果为16
。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push((function (n) { return function () { return n * n; } })(i)); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; f1(); // 1 f2(); // 4 f3(); // 9
注意这里用了一个“创建一个匿名函数并立刻执行”的语法:
(function (x) { return x * x; })(3); // 9
理论上讲,创建一个匿名函数并立刻执行可以这么写:
function (x) { return x * x } (3);
但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:
(function (x) { return x * x }) (3);
通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:
(function (x) { return x * x; })(3);
说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?
当然不是!闭包有非常强大的功能。举个栗子:
在面向对象的程序设计语言里,比如Java和C++,要在对象内部封装一个私有变量,可以用private
修饰一个成员变量。
在没有class
机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器:
'use strict'; function create_counter(initial) { var x = initial || 0; return { inc: function () { x += 1; return x; } } }
它用起来像这样:
var c1 = create_counter(); c1.inc(); // 1 c1.inc(); // 2 c1.inc(); // 3 var c2 = create_counter(10); c2.inc(); // 11 c2.inc(); // 12 c2.inc(); // 13
在返回的对象中,实现了一个闭包,该闭包携带了局部变量x
,并且,从外部代码根本无法访问到变量x
。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)
函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2
和pow3
:
'use strict'; function make_pow(n) { return function (x) { return Math.pow(x, n); } } // 创建两个新函数: var pow2 = make_pow(2); var pow3 = make_pow(3); console.log(pow2(5)); // 25 console.log(pow3(7)); // 343
闭包的缺点: 内存泄漏 需要手动释放内存
isNaN(NaN) 来判断类型是不是NaN
浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
null和undefined
字符串 \
转义字符
多行字符串用反引号 `
模板字符串
var name = '小明'; var age = 20; console.log(`你好, ${name}, 你今年${age}岁了!`);
字符串操作 s=“Hello,world!”
数组
indexOf
slice
push
pop
unshift
shift
sort 排序有坑
reverse 反转
splice 修改数组的万能方法
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle']; // 从索引2开始删除3个元素,然后再添加两个元素: arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite'] arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle'] // 只删除,不添加: arr.splice(2, 2); // ['Google', 'Facebook'] arr; // ['Microsoft', 'Apple', 'Oracle'] // 只添加,不删除: arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素 arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
concat 连接两个数组放回一个新数组 不修改原数组
join 用指定的字符串连接
对象
要判断一个属性是否是xiaoming
自身拥有的,而不是继承得到的,可以用hasOwnProperty()
方法:
var xiaoming = { name: '小明' }; xiaoming.hasOwnProperty('name'); // true xiaoming.hasOwnProperty('toString'); // false
Map和Set ES6
JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。
Map
是一组键值对的结构,具有极快的查找速度。
var m = new Map(); // 空Map m.set('Adam', 67); // 添加新的key-value m.set('Bob', 59); m.has('Adam'); // 是否存在key 'Adam': true m.get('Adam'); // 67 m.delete('Adam'); // 删除key 'Adam' m.get('Adam'); // undefined
set
var s = new Set([1, 2, 3, 3, '3']); s; // Set {1, 2, 3, "3"}
通过add(key)
方法可以添加元素到Set
中,可以重复添加,但不会有效果:
通过delete(key)
方法可以删除元素:
iterable ES6
ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型。
具有iterable
类型的集合可以通过新的for ... of
循环来遍历。
for…in 取的是key
问题 var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x in a) { console.log(x); // '0', '1', '2', 'name' }
for…of 遍历自身
foreach
var s = new Set(['A', 'B', 'C']); s.forEach(function (element, sameElement, set) { console.log(element); });
使用构造函数中的方法definePrpperty
obj = {}
Object.defineProperty(obj,‘name’,{
set:function(){
console.log(‘属性发生改变’)
}
})
obj.name=‘值’ //执行set方法进行监听属性
https://blog.csdn.net/qq_43958325/article/details/112315992
https://blog.csdn.net/qq_39414417/article/details/104763824
https://blog.csdn.net/qq2276031/article/details/106407647
https://juejin.cn/post/6844904067370598413#heading-0
https://zhuanlan.zhihu.com/p/248248674
数据绑定
https://segmentfault.com/a/1190000006599500
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n0IOnAgJ-1641822732986)(image/bVBQYu)]
https://blog.csdn.net/sinat_27346451/article/details/78315075