1、基础数据类型和引用数据类型
基本类型有6种,分别是undefined,null,bool,string,number,symbol(ES6新增)。 虽然 typeof null 返回的值是 object,但是null不是对象,而是基本数据类型的一种。 基本数据类型存储在栈内存,存储的是值。 复杂数据类型的值存储在堆内存,地址(指向堆中的值)存储在栈内存。当我们把对象赋值给另外一个变量的时候,复制的是地址,指向同一块内存空间,当其中一个对象改变时,另一个对象也会变化。
引用数据类型:array、object、function,引用数据类型也是数据类型
//栈和队列的区别:
栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
栈是先进后出,队列是先进先出。
栈只允许在表尾一端进行插入和删除,队列只允许在表尾一端进行插入,在表头一端进行删除。
//栈和堆的区别:
栈区:由编辑器自动分配释放,存放函数的参数值,局部变量的值等(基本类型值)。
堆区:由程序员分配释放,若程序员不释放,程序结束时可能有OS回收(引用类型值)。
栈(数据结构):一种先进后出的数据结构。
堆(数据结构):堆可以被看成是一棵树,如:堆排序。
数组array排序方法:sort()、reverse()
① sort() 不传参时默认为升序,且是按字符串比较的方式排序;传参时,其参数为函数,且该函数带俩参数
a 和 b,返回值 a - b 为升序,b - a为降序
② reverse() 和 sort() 两函数均会改变原数组,且返回值同样也是改变后的数组
2、数据类型转换
二、类型转换
1)显示类型转换:
a. Number函数
b. String函数
c.Boolean函数
2) 显示类型转换 Number函数
当原始类型转换时,分别对应以下情况:
数值:转换后还是原来的值。
字符串:如果可以被解析为数值,则转换为相应的数值,否则得到NaN,空字符串转换为0
布尔值:true转成1, false转成0
undefined: 转成NaN
null: 转成0
当为对象类型转换时,遵循以下方式:
a.先调用对象自身的valueOf方法,如果改方法返回原始类型的值(数值/字符串/boolean等),则直接对该值使用 Number方法,不在进行后续步骤。
b.如果valueOf方法返回复合类型的值,在调用对象自身的toString方法,如果toString方法返回原始类型的值,则对该值使用Number方法,不在进行后续步骤。
c.如果toString方法返回的时复合类型的值,则报错.
如例: var a = {b: 1}; Number(a); // 输出NaN,按照如上步骤规则,则
首先a.valueOf() 输出{b: 1}, 返回不是原始数据类型,则调用toString方法,返回“[object Object]”, 返回字符串类型,Number('[object, object]') 则输出NaN。
3)显示类型转换 String函数
当转换为原始类型数据时,按照如下规则:
数值:转换为相应的字符串
字符串: 转换后还是原来的值
布尔值: true转换为‘true’, false转换为‘false’
undefined: 转换为“undefined”
null: 转换为‘null’
当转换类型为对象类型时,按照如下规则转换:
a.先调用toString方法,如果toString返回的时原始数据类型,则对该值使用toString方法,不在进行以下步骤。
b.如果toString方法返回对是复合类型的值,在调用valueOf方法,如果valueOf方法返回的是原始数据类型的值,则对该值使用String方法,不在进行以下步骤。
c.如果valueOf方法返回的是复合类型的值,则报错。
4)显示类型转换 Boolean函数
当转换数据为原始数据类型是,按照如下规则转换
当为null、undifined、NaN、0、‘’,转换为false,其他转换都为true
5)隐式类型转换,如四则运算/判断语句/Native调用,转换与以上转换规则相同.
输出以下结果:
[] + [];
[] + {};
{} + [];
{} + {};
true + true;
1 + {a: 1};
数字 + boolean/null -> 数字 数字 + undefined -> NaN [1].toString() === '1' {}.toString() === '[object object]' NaN !== NaN 、+undefined 为 NaN
3、为什么 0.1 + 0.2 != 0.3,如何解决
原因:因为 JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有该问题。
解决方法:将浮点数转化成整数计算。因为整数都是可以精确表示的。
4、js判断数据类型的方法
//方法一:typeof ,无法检验出数组、对象或null typeof 2 // number typeof null // object typeof {} // object typeof [] // object typeof (function(){}) // function typeof undefined // undefined typeof '222' // string typeof true // boolean //方法二:instanceof ,只能用来判断数组和对象,不能判断string和boolean类型,数组也属于对象。 var o = {'name':'lee'}; var a = ['reg','blue']; console.log(o instanceof Object);// true console.log(a instanceof Array);// true console.log(o instanceof Array);// false //由于数组也属于对象,不能区分是数组还是对象,改进:封装一个方法判断数组和对象 var o = {'name':'lee'}; var a = ['reg','blue']; var getDataType = function(o){ if(o instanceof Array){ return 'Array' }else if( o instanceof Object ){ return 'Object'; }else{ return 'param is no object type'; } }; console.log(getDataType(o));//Object。 console.log(getDataType(a));//Array。 //方法三:constructor方法 var o = {'name':'lee'}; var a = ['reg','blue']; console.log(o.constructor == Object);//true console.log(a.constructor == Array);//true //方法四:toString()判断string和boolean类型 var o = {'name':'lee'}; var a = ['reg','blue']; function c(name,age){ this.name = name; this.age = age; } var c = new c('kingw','27'); console.log(Object.prototype.toString.call(a));//[object Array] console.log(Object.prototype.toString.call(o));//[Object Object] console.log(Object.prototype.toString.call(c));//[Object Object]
5、js原型链
每个对象拥有一个原型对象,通过 proto (读音: dunder proto) 指针指向其原型对象,并从中继承方法和属性,
同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.proptotype.__proto__ 指向的是null)。
这种关系被称为原型链 (prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。
6、手动实现继承
1、原型链继承 核心: 将父类的实例作为子类的原型 特点: 非常纯粹的继承关系,实例是子类的实例,也是父类的实例 父类新增原型方法/原型属性,子类都能访问到 缺点: 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中 无法实现多继承 来自原型对象的引用属性是所有实例共享的 创建子类实例时,无法向父类构造函数传参// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
alert(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
alert(this.name + '正在吃:' + food);
};
function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); alert(cat.name); alert(cat.eat('fish')); alert(cat.sleep()); alert(cat instanceof Animal); //true alert(cat instanceof Cat); //true
2、借用构造函数继承 核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型) 特点: 解决了1中,子类实例共享父类引用属性的问题 创建子类实例时,可以向父类传递参数 可以实现多继承(call多个父类对象)(不完美,没有父类方法) 缺点: 实例并不是父类的实例,只是子类的实例 只能继承父类的实例属性和方法,不能继承原型属性/方法 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能 // 定义一个动物类 function Animal (name) { // 属性 this.name = name || 'Animal'; // 实例方法 this.sleep = function(){ alert(this.name + '正在睡觉!'); } }
// 原型方法
Animal.prototype.eat = function(food) {
alert(this.name + '正在吃:' + food);
};
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); alert(cat.name); alert(cat.sleep()); alert(cat instanceof Animal); // false alert(cat instanceof Cat); // true
3、组合继承 核心:通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,继承父类属性和方法,实现函数复用 特点: 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法 既是子类的实例,也是父类的实例 不存在引用属性共享问题 可传参 函数可复用 可以实现多继承(同上) 缺点: 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)(仅仅多消耗了一点内存) // 定义一个动物类 function Animal (name) { // 属性 this.name = name || 'Animal'; // 实例方法 this.sleep = function(){ alert(this.name + '正在睡觉!'); } } // 原型方法 Animal.prototype.eat = function(food) { alert(this.name + '正在吃:' + food); }; function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); // Test Code var cat = new Cat(); alert(cat.name); alert(cat.sleep()); alert(cat instanceof Animal); // true alert(cat instanceof Cat); // true
6、寄生组合继承 核心:通过寄生方式,创建空函数,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点 特点: 堪称完美 缺点: 实现较为复杂 不能实现多继承(不完美,同上) // 定义一个动物类 function Animal (name) { // 属性 this.name = name || 'Animal'; // 实例方法 this.sleep = function(){ alert(this.name + '正在睡觉!'); } } function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } function aaron(o,y){ var Super = function(){}; Super.prototype=y.prototype; o.prototype=new Super(); o.prototype.constructor=o; } aaron(Cat,Animal); // Test Code var cat = new Cat(); alert(cat.name); cat.sleep(); alert(cat instanceof Animal); // true alert(cat instanceof Cat); //true
7、call,apply,bind的区别?
call 和 apply 的功能相同,区别在于传参的方式不一样:
1)、bind会产生新的函数,(把对象和函数绑定死后,产生新的函数)
2)、call和apply不会产生新的函数,只是在调用时,绑定一下而已。
3)、call和apply的区别,第一个参数都是要绑定的this,apply第二个参数是数组(是函数的所有参数),call把apply的第二个参数单列出来。
8、闭包,应用场景,优缺点?
概念:
闭包根据函数定义的位置决定是否形成闭包
能够读取其他函数内部变量的函数。
或简单理解为定义在一个函数内部的函数,内部函数持有外部函数内变量的引用。
闭包用途 1、读取函数内部的变量 2、让这些变量的值始终保持在内存中。不会再f1调用后被自动清除。 3、方便调用上下文的局部变量。利于代码封装。 原因:f1是f2的父函数,f2被赋给了一个全局变量,f2始终存在内存中,f2的存在依赖f1,因此f1也始终存在内存中,不会在调用结束后,被垃圾回收机制回收。
闭包缺点 1、由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2、闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),
把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
闭包应用场景之setTimeout
//setTimeout传递的第一个函数不能带参数 setTimeout((param) => { alert(param) }, 1000); //通过闭包可以实现传参效果 function func(param) { return function() { alert(param) } } var f1 = func('汪某'); setTimeout(f1, 1000)//汪某
9、垃圾回收?什么算法?
JS 有自动垃圾回收机制,那么这个自动垃圾回收机制的原理是什么呢? 其实很简单,就是找出那些不再继续使用的值,然后释放其占用的内存。 大多数内存管理的问题都在这个阶段。 在这里最艰难的任务是找到不再需要使用的变量。 不再需要使用的变量也就是生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在, 当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。 垃圾回收算法主要依赖于引用的概念。 在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。 例如,一个Javascript对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)。 在这里,“对象”的概念不仅特指 JavaScript 对象,还包括函数作用域(或者全局词法作用域)。 //引用计数垃圾收集 这是最初级的垃圾回收算法。 引用计数算法定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。 如果没有其他对象指向它了,说明该对象已经不再需了。 上面这种JS写法再普通不过了,创建一个DOM元素并绑定一个点击事件。 此时变量 div 有事件处理函数的引用,同时事件处理函数也有div的引用!(div变量可在函数内被访问)。 一个循序引用出现了,按上面所讲的算法,该部分内存无可避免的泄露了。 为了解决循环引用造成的问题,现代浏览器通过使用标记清除算法来实现垃圾回收。 //标记清除算法 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。 简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。 从这个概念可以看出,无法触及的对象包含了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)。 但反之未必成立。 工作流程: 垃圾收集器会在运行的时候会给存储在内存中的所有变量都加上标记。 从根部出发将能触及到的对象的标记清除。 那些还存在标记的变量被视为准备删除的变量。 最后垃圾收集器会执行最后一步内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间。
内存泄漏
//堆栈溢出有什么区别? 内存泄漏? 那些操作会造成内存泄漏?怎么样防止内存泄漏? 堆栈溢出:就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据越界,结果覆盖了别的数据。经常会在递归中发生。 内存泄露是指:用动态存储分配函数内存空间,在使用完毕后未释放,导致一直占据该内存单元。直到程序结束。指任何对象在您不再拥有或需要它之后仍然存在。 //造成内存泄漏: setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。 闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环) //防止内存泄露: 1、不要动态绑定事件; 2、不要在动态添加,或者会被动态移除的dom上绑事件,用事件冒泡在父容器监听事件; 3、如果要违反上面的原则,必须提供destroy方法,保证移除dom后事件也被移除,这点可以参考Backbone的源代码,做的比较好; 4、单例化,少创建dom,少绑事件。
10、对象遍历
//1、Object.keys()获取键名数组 //2、Object.getOwnPropertyNames()获取键名数组 //3、Object.entries()获取键值对数组 //4、Object.values()获取对象的属性值数组 //5、Object.getOwnPropertySymbols()获取Symbol属性名
//Object.keys()获取键名数组 //使用Object.keys()可以获取到对象实例的所有可枚举属性,其返回值为一个数组,数组元素为对象的键名: let father = { fatherAttr: 1 }; // 以father为原型创建对象实例instance let instance = Object.create(father); instance.a = 1; instance.b = 1; instance.c = 1; Object.defineProperty(instance, 'd', { writable: true, value: 1, enumerable: false, configurable: true }); for (let key of Object.keys(instance)) { console.log(key); } // a // b // c
//Object.getOwnPropertyNames()获取键名数组 //此方法跟keys方法表现一样,所不同的是,其返回的数组包含了对象的不可枚举属性: let father = { fatherAttr: 1 }; let instance = Object.create(father); instance.a = 1; instance.b = 1; instance.c = 1; Object.defineProperty(instance, 'd', { writable: true, value: 1, enumerable: false, configurable: true }); for (let key of Object.getOwnPropertyNames(instance)) { console.log(key); } // a // b // c // d
//Object.entries()获取键值对数组 let father = { fatherAttr: 1 }; let instance = Object.create(father); instance.a = 1; instance.b = 1; instance.c = 1; Object.defineProperty(instance, 'd', { writable: true, value: 1, enumerable: false, configurable: true }); for (let key of Object.entries(instance)) { console.log(key); } // ["a", 1] // ["b", 1] // ["c", 1] //所以当使用一个对象初始化一个Map实例时,可以使用这个方法: let obj = { a: 1, b: 1, c: 1 }, map = new Map(Object.entries(obj)); console.log(map.get('a')); console.log(map.get('b')); console.log(map.get('c')); // 1 // 1 // 1
//Object.values()获取对象的属性值数组
//Object.getOwnPropertySymbols()获取Symbol属性名 //上面提到的几个方法都无法获取到对象实例的Symbol类型的属性名,如果需要遍历这个玩意,需要使用Object.getOwnPropertySymbols()方法: let father = { fatherAttr: 1 }; let instance = Object.create(father); instance.a = 1; instance.b = 1; instance.c = 1; instance[Symbol('I am a handsome boy!')] = 1; for (let key of Object.keys(instance)) { console.log(key); } // a // b // c for (let key of Object.getOwnPropertySymbols(instance)) { console.log(key); } // Symbol(I am a handsome boy!)
11、给定一个对象,如何遍历获取里面的属性和值?
var obj = {name0: "11", name1: "22", name2: "33"}; for (var key in obj){ console.log(key +':'+ obj[key]) }
forEach、map、filter、reduce区别
//1.相同点: 都会循环遍历数组中的每一项; map()、forEach()和filter()方法里每次执行匿名函数都支持3个参数,参数分别是:当前元素、当前元素的索引、当前元素所属的数组; 匿名函数中的this都是指向window; 只能遍历数组。 //2.不同点: map()速度比forEach()快; map()和filter()会返回一个新数组,不对原数组产生影响;forEach()不会产生新数组,返回undefined;
reduce()函数是把数组缩减为一个值(比如求和、求积);reduce()有4个参数,第一个参数为初始值。
手写实现filter()
Arrar.prototype.myFilter = function (fn) { let arr = []; for(let i=0; i<this.length;i++) { let item = this[i] if(fn(item,i,this)) { arr.push(this[i]) } } return arr } //利用 myFilter实现数组去重 let arr1 = [1,2,3,4,5,1,2,3,4,5]; let res = arr1.myFilter((item,index,arr) => { return arr.indexOf(item) === index })
手写实现map()
Array.prototype.myMap = function (fn) { let arr = []; for(let i=0; i<this.length;i++) { let item = fn(this[i],i,this) { arr.push(item) } } return arr }
给定1个URL数组,分别实现并发及顺序依次执行请求,手写实现fetch(url)
function sendRequest(urls, max, callback) { let allUrls = [...urls], i = 0, fetchArr = []; function doFetch() { // 处理边界的情况 if (i === allUrls.length) { return Promise.resolve(); } //每次调用出去 一个 url, 放入fetch中 let one = fetch(allUrls[i++]); // 将此promise的状态保存在fetchArr中, 执行完之后 从数组中删除。 let result = one.then(() => fetchArr.splice(fetchArr.indexOf(result), 1)); result.push(fetchArr); // 数量不够就重复调用doFetch, 够了的话就比较 let p = Promise.resolve(); if (fetchArr.length >= max) { p = Promise.race(fetchArr); } return p.then(() => doFetch()) } // 最后用all 处理剩余数组中的,等处理完再执行callback return doFetch().then(() => Promise.all(fetchArr)).then(() => { callback(); }) }
12、数组遍历
//JavaScript 的 4 种数组遍历方法: for 、 forEach() 、 for/in 、 for/of、map遍历、filter遍历
//1、使用for和for/in,我们可以访问数组的下标,而不是实际的数组元素值: for (let i = 0; i < arr.length; ++i) { console.log(arr[i]); } for (let i in arr) { console.log(arr[i]); } //2、使用for/of,则可以直接访问数组的元素值: for (const v of arr) { console.log(v); } //3、使用forEach(),则可以同时访问数组的下标与元素值: arr.forEach((v, i) => console.log(v)); //要点: for/in与forEach会跳过空元素 for,for/in与for/of会保留外部作用域的this。 //对于forEach, 除非使用箭头函数,它的回调函数的 this 将会变化。 //简单地说,for/of是遍历数组最可靠的方式,它比for循环简洁,并且没有for/in和forEach()那么多奇怪的特例。 //for/of的缺点是我们取索引值不方便,而且不能这样链式调用forEach(). forEach()。 //4、map遍历 //有返回值,可以return出来 //map的回调函数中支持return返回值;return的是啥,相当于把数组中的这一项变为啥(并不影响原来的数组,只是相当于把原数组克隆一份,把克隆的这一份的数组中的对应项改变了); var ary = [12,23,24,42,1]; var res = ary.map(function (item,index,ary ) { return item*10; }) console.log(res);//-->[120,230,240,420,10]; 原数组拷贝了一份,并进行了修改 console.log(ary);//-->[12,23,24,42,1]; 原数组并未发生变化 //5、filter()遍历 不会改变原始数组,返回新数组 var arr = [73,84,56, 22,100] var newArr = arr.filter(item => item>80) //得到新数组 [84, 100] console.log(newArr,arr);
//6.every遍历 every()是对数组中的每一项运行给定函数,如果该函数对每一项返回true,则返回true。 var arr = [ 1, 2, 3, 4, 5, 6 ]; console.log( arr.every( function( item, index, array ){ return item > 3; })); false //7.some遍历 some()是对数组中每一项运行指定函数,如果该函数对任一项返回true,则返回true。 var arr = [ 1, 2, 3, 4, 5, 6 ]; console.log( arr.some( function( item, index, array ){ return item > 3; })); true //8.reduce reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值。 var total = [0,1,2,3,4].reduce((a, b)=>a + b); //10 reduce接受一个函数,函数有四个参数,分别是:上一次的值,当前值,当前值的索引,数组 [0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){ return previousValue + currentValue; }); reduce还有第二个参数,我们可以把这个参数作为第一次调用callback时的第一个参数,
上面这个例子因为没有第二个参数,所以直接从数组的第二项开始,如果我们给了第二个参数为5,那么结果就是这样的: [0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){ return previousValue + currentValue; },5); 第一次调用的previousValue的值就用传入的第二个参数代替, //9.reduceRight reduceRight()方法的功能和reduce()功能是一样的,不同的是reduceRight()从数组的末尾向前将数组中的数组项做累加。 reduceRight()首次调用回调函数callbackfn时,prevValue 和 curValue 可以是两个值之一。
如果调用 reduceRight() 时提供了 initialValue 参数,则 prevValue 等于 initialValue,curValue 等于数组中的最后一个值。
如果没有提供 initialValue 参数,则 prevValue 等于数组最后一个值, curValue 等于数组中倒数第二个值。 var arr = [0,1,2,3,4]; arr.reduceRight(function (preValue,curValue,index,array) { return preValue + curValue; }); // 10 回调将会被调用四次,每次调用的参数及返回值如下: 如果提供一个初始值initialValue为5: var arr = [0,1,2,3,4]; arr.reduceRight(function (preValue,curValue,index,array) { return preValue + curValue; }, 5); // 15 回调将会被调用五次,每次调用的参数及返回的值如下: 同样的,可以对一个数组求和,也可以使用reduceRight()方法: var arr = [1,2,3,4,5,6]; console.time("ruduceRight"); Array.prototype.ruduceRightSum = function (){ for (var i = 0; i < 10000; i++) { return this.reduceRight (function (preValue, curValue) { return preValue + curValue; }); } } arr.ruduceRightSum(); console.log('最终的值:' + arr.ruduceSum()); // 21 console.timeEnd("ruduceRight"); // 5.725ms //10.find find()方法返回数组中符合测试函数条件的第一个元素。否则返回undefined var stu = [ { name: '张三', gender: '男', age: 20 }, { name: '王小毛', gender: '男', age: 20 }, { name: '李四', gender: '男', age: 20 } ] function getStu(element){ return element.name == '李四' } stu.find(getStu) //返回结果为 //{name: "李四", gender: "男", age: 20} //ES6方法 stu.find((element) => (element.name == '李四')) //11.findIndex 对于数组中的每个元素,findIndex 方法都会调用一次回调函数(采用升序索引顺序),直到有元素返回 true。
只要有一个元素返回 true,findIndex 立即返回该返回 true 的元素的索引值。如果数组中没有任何元素返回 true,则 findIndex 返回 -1。 findIndex 不会改变数组对象。 [1,2,3].findIndex(function(x) { x == 2; }); // Returns an index value of 1. 1 2 [1,2,3].findIndex(x => x == 4); // Returns an index value of -1. //12.keys,values,entries //ES6 提供三个新的方法 —— entries(),keys()和values() —— 用于遍历数组。
它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历 for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
13、如何判断一个变量是不是数组
//方法一: 使用instanceof方法 //instanceof判断原理:通过arr的原型链(__proto__)一层层往上进行查询,能否找到对应的构造函数的原型对象(prototype),如果找到了就返回true,反之false。 console.log(arr instanceof Array); // true console.log(obj instanceof Array); // false //方法二:使用Array.prototype.isPrototypeOf()方法 //利用isPrototypeOf()方法,判定Array是不是在obj的原型链中,如果是,则返回true,否则false。 console.log(Array.prototype.isPrototypeOf(arr)); // true console.log(Array.prototype.isPrototypeOf(obj)); // false //方法三:使用constructor方法 //constructor构造函数指向的是创建它的构造函数。 console.log(arr.constructor === Array); // true console.log(obj.constructor === Array); // false //方法四:使用Array.isArray()方法 (ES6最新方法) console.log(Array.isArray(arr)); // true console.log(Array.isArray(obj)); // false //方法五:使用Object.prototype.toString.call()方法(推荐) //使用Object.prototype上的原生toString()方法判断数据类型 console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true console.log(Object.prototype.toString.call(obj) === '[object Object]'); // true //这里思考为什么不直接使用toString()方法呢? //因为toString为Object的原型方法,而Array ,function等类型作为Object的实例,都重写了toString方法,示例如下: let array = [1,2,3]; arr.toString(); // 已经被重写输出:1,2,3 Object.prototype.toString.call(arr); // 最原始的输出:"[object Array]"
14、instanceof原理了解吗?
//在 MDN 上是这样描述 instanceof 的: //instanceof 运算符用于测试构造函数的 prototype 属性是否出现在对象原型链中的任何位置 //换句话说,如果A instanceof B,那么 A 必须是一个对象,而 B 必须是一个合法的 JavaScript 函数。在这两个条件都满足的情况下: //判断 B 的 prototype 属性指向的原型对象(B.prototype)是否在对象 A 的原型链上。 //如果在,则为 true;如果不在,则为 false。 下面我们举一个例子一步步来说明: function Person() {} const p1 = new Person(); p1 instanceof Person; // true
//第一步:每一个构造函数都有一个 prototype 属性。 //第二步:这个 prototype 属性指向这个构造函数的原型对象 //第三步:通过 new 关键字,可以创建一个构造函数的实例(这里是 p1),而实例上都有一个 __proto__ 属性 //第四步:实例上的 __proto__ 属性也指向构造函数的原型对象,这样我们就可以得到一张完整的关系图了 //第五步:p1 instanceof Person ,检查 B(Person) 的 prototype 属性指向的原型对象,是否在对象 A(p1) 的原型链上。 //经过我们的一步步分解,发现 B(Person) 的 prototype 所指向的原型对象确实在 A(p1) 的原型链上,所以我们可以确定 p1 instanceof Person 一定是为 true 的。 //我们再深入一点会发现,不仅仅 p1 instanceof Person 为 true ,p1 instanceof Object 也为 true ,这又是为什么呢? //其实,Person 的原型对象上也有一个 __proto__ 属性,而这个属性指向 Object 的 prototype 属性所指向的原型对象,我们可以在控制台打印一下: 既然有这个关系,那我们再完善一下上面的图:
//通过 Person 的例子,我们知道构造函数 Object 上的 prototype 属性会指向它的原型对象: //现在,我们要判断 p1 instanceof Object 的真假,还记得上面的定义么?我们再来一遍: //判断 B 的 prototype 属性指向的原型对象(B.prototype)是否在对象 A 的原型链上。 //如果在,则为 true;如果不在,则为 false。 //此时,我们发现 B(Object) 的 prototype 属性所指向的原型对象依然在 A(p1) 的原型链上,所以结果为 true 。 //所有 JavaScript 对象都有 __proto__ 属性,只有 Object.prototype.__proto__ === null ; 构造函数的 prototype 属性指向它的原型对象,而构造函数实例的 __proto__ 属性也指向该原型对象;
15、js事件捕获和时间冒泡
事件流分为两种,捕获事件流和冒泡事件流。
捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点。
冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点。
DOM事件流分为三个阶段,一个是捕获节点,一个是处于目标节点阶段,一个是冒泡阶段。
阻止冒泡(捕获)
w3c标准:event.stopPropagation
IE:event.cancelBunnle=true
阻止默认行为
W3C标准.event.preventDefault()
IE event.returnValue = false
16、js事件委托或事件代理
简介:事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,
通过判断事件发生元素DOM的类型,来做出不同的响应。e.target监听点击是哪个元素节点
举例:最经典的就是ul和li标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加。
好处:
-
-
添加到页面上的事件数量会影响页面的运行性能,如果添加的事件过多,会导致网页的性能下降。采用事件代理的方式,可以大大减少注册事件的个数。
-
事件代理的当时,某个子孙元素是动态增加的,不需要再次对其进行事件绑定。
-
不用担心某个注册了事件的DOM元素被移除后,可能无法回收其事件处理程序,我们只要把事件处理程序委托给更高层级的元素,就可以避免此问题。
-
addEventListener
绑定事件方法的第三个参数,就是控制事件触发顺序是否为事件捕获。true,事件捕获;false,事件冒泡。默认false,即事件冒泡。
17、js数组操作方法
https://juejin.im/post/5b0903b26fb9a07a9d70c7e0#heading-25
基于原生JS封装数组原型上的sort方法
Array.prototype.mySort = function(fn){ if(Object.prototype.toString.call(fn)==='[object Function]'){ //如果传进来参数的是函数 for(var i = 0;i<this.length-1;i++){ //遍历数组,将前后两项作为实参传给fn if(fn.call(this,this[i],this[i+1])>0){ //如果fn执行之后的返回值大于0.就调用swap方法交换位置 var a = this[i],b=this[i+1]; this[i] = swap(a,b).a; this[i+1] = swap(a,b).b; //交换之后,如果当前项不是第一项,则当前项(索引为i的项)继续跟前面的项进行比较 if(i>0){ for(var j = i-1;j>=0;j--){ if(fn.call(this,this[j],this[j+1])>0){ var a = this[j],b=this[j+1]; this[j] = swap(a,b).a; this[j+1] = swap(a,b).b; } } } } } }else{ //如果不是函数,则按正常排序 //遍历数组,将前后两项进行比较 for(var i = 0;i<this.length-1;i++){ var cur = this[i];//当前项 var next = this[i+1];//下一项 if(comASCII(cur,next)){ //当返回true的时候交换,并且交换完成之后,当前项继续往前比较 this[i] = swap(cur,next).a; this[i+1] = swap(cur,next).b; //当前项继续向前比较 if(i>0){ for(var k = i-1;k>=0;k--){ var cur = this[k]; var next = this[k+1]; if(comASCII(cur,next)){ this[k] = swap(cur,next).a; this[k+1] = swap(cur,next).b; } } } } } } //封装一个交换位置的函数 function swap(a,b){ return { a:b, b:a } } //如果不传参的情况下比较ASCII码 function comASCII(cur,next){ //全部转换为字符串、逐项比较ASCII码 cur = cur.toString(); next = next.toString(); //取长度最大值 var len = cur.length>next.length?next.length:cur.length; //当前后两项都不是不是{}类型的数据时,进行比较 if(cur!=='[object Object]'&&next!=='[object Object]'){ for(var j = 0;j){ if(!isNaN(cur.charCodeAt(j))&&!isNaN(next.charCodeAt(j))){ //如果二者的ASCII码都是有效数字 if(cur.charCodeAt(j)>next.charCodeAt(j)){ //如果前一项比后一项当前的ASCII码大,则返回true,交换位置 return true; }else if(cur.charCodeAt(j)==next.charCodeAt(j)){ //如果相等直接进入下一轮循环 continue; }else{ //前项比后项小,直接返回false return false; } } } if(!isNaN(cur.charCodeAt(len))&&isNaN(next.charCodeAt(len))&&(cur.charCodeAt(len-1)==next.charCodeAt(len-1))){ //比较完之后,如果前一项ASCII还是有效数字,说明前项比后项大,交换 return true; } } //如果上述条件不满足,则不交换 return false; } //返回当前数组 return this; };
18、数组的哪些API会改变原数组
数组的哪些API会改变原数组? 改变的有: splice/reverse/fill/copyWithin/sort/push/pop/unshift/shift 不改变的有: slice/map/forEach/every/filter/reduce/entries/find
19、js获取属性节点
https://microzz.com/2017/04/06/jsdom/
document.querySelector(selectors) //接受一个CSS选择器作为参数,返回第一个匹配该选择器的元素节点。 document.querySelectorAll(selectors) //接受一个CSS选择器作为参数,返回所有匹配该选择器的元素节点。 document.getElementsByTagName(tagName) //返回所有指定HTML标签的元素 document.getElementsByClassName(className) //返回包括了所有class名字符合指定条件的元素 document.getElementsByName(name) //用于选择拥有name属性的HTML元素(比如 document.getElementById(id) //返回匹配指定id属性的元素节点。 document.elementFromPoint(x,y) //返回位于页面指定位置最上层的Element子节点。
20、js的各种位置,比如clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop的区别?
clientHeight:表示的是可视区域的高度,不包含border和滚动条
offsetHeight:表示可视区域的高度,包含了border和滚动条
scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。
clientTop:表示边框border的厚度,在未指定的情况下一般为0
scrollTop:滚动后被隐藏的高度,获取对象相对于由offsetParent属性指定的父坐标(css定位的元素或body元素)距离顶端的高度。
21、this指向/this绑定
this,函数执行的上下文,可以通过apply,call,bind改变this的指向。
对于匿名函数或者直接调用的函数来说,this指向全局上下文(浏览器为window,nodejs为global),剩下的函数调用,那就是谁调用它,this就指向谁。
当然还有es6的箭头函数,箭头函数的指向取决于该箭头函数声明的位置,在哪里声明,this就指向哪里。
this的绑定规则有四种:默认绑定,隐式绑定,显式绑定,new绑定. 函数是否在 new 中调用(new绑定),如果是,那么 this 绑定的是新创建的对象【前提是构造函数中没有返回对象或者是function,否则this指向返回的对象/function】。
函数是否通过 call,apply 调用,或者使用了 bind (即硬绑定),如果是,那么this绑定的就是指定的对象。
函数是否在某个上下文对象中调用(隐式绑定),如果是的话,this 绑定的是那个上下文对象。一般是 obj.foo()
如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到 undefined,否则绑定到全局对象。 如果把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind, 这些值在调用时会被忽略,实际应用的是默认绑定规则。
箭头函数没有自己的 this, 它的this继承于上一层代码块的this。
22、new操作符的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别?
new过程:代码 new Person(…) 执行时,会发生以下事情:
1. 创建一个类的实例:创建一个空对象obj,然后把这个空对象的__proto__设置为Person.prototype(即构造函数的prototype),
2. 初始化实例:构造函数Person被传入参数并调用(为这个新对象添加属性),关键字this被设定指向该实例obj;
3. 返回实例obj。
MDN上的解释:
1、一个继承自 Person.prototype 的新对象被创建。
2、使用指定的参数调用构造函数 Person ,并将 this 绑定到新创建的对象。new Person 等同于 new Person(),也就是没有指定参数列表,Person 不带任何参数调用的情况。
3、由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。
(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建
字面量创建对象,不会调用 Object构造函数, 简洁且性能更好; new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。 通过对象字面量定义对象时,不会调用Object构造函数。
手写实现new操作符?
手动实现new: function person (sex, weight, color){ this.sex = sex this.weight = weight this.color = color } function newSelf(fn) { // 第一步:创建一个新对象 var obj = {} // 新对象继承Person.prototype obj.__proto__ = fn.prototype return function () { // 使用指定的参数调用构造函数 Person ,并将 this 绑定到新创建的对象 fn.apply(obj,arguments) return obj } } var person2 = newSelf (person)('男', '75kg', 'yellow') console.log(person2.__proto__ == person.prototype) //true
23、什么是变量提升?
变量提升就是变量在声明之前就可以使用,值为undefined。 在代码块内,使用 let/const 命令声明变量之前,该变量都是不可用的(会抛出错误)。这在语法上,称为“暂时性死区”。
暂时性死区也意味着 typeof 不再是一个百分百安全的操作。
js作用域说一下?
//全局作用域 var a = 0; if (true) { var b = 1; } console.log(b); // 输出1 像这样在全局中定义a变量,为全局变量,在任何地方都能访问到这个a变量。 因为js中没有块级作用域,所以在 if 或是 for 这样逻辑语句中定义的变量都是可以被外界访问到的。 //局部作用域 局部作用域也可以称之为函数作用域。 function fn () { var c = 2; } console.log(c); // 报错,c变量未定义 局部作用域中定义的变量,只供局部作用域调用,外界无法访问。 //作用域链 Function对象有一个仅供 JavaScript 引擎存取的内部属性。 这个属性就是[[Scope]]。[[Scope]]包含了一个函数被创建的作用域中对象的集合。这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。 关于作用域链,局部作用域可以访问到全局作用域中的变量和方法,而全局作用域不能访问局部作用域的变量和方法。 var a = 0; function fn () { var b = 1; console.log(a); // 输出 1 } // 全局作用域并不能访问 fn 函数中定义的 b 变量 console.log(b); // 报错 fn(); 当函数fn()创建时,它的作用域中插入了一个对象变量,这个全局对象代表所有在全局范围内定义的变量。 当函数fn()执行时,会创建一个名为执行环境的独一无二的内部对象。函数每执行一次,都会创建一个执行环境。当函数执行完毕,执行环境就会被销毁。 每个执行环境都有自己的作用域链,用来解析标识符。当执行环境被创建时,它的作用域就会初始化为当前运行函数的[[Scope]]属性中的对象。 执行环境创建完成之后,就会生成一个"活动对象",这个对象包含了当前函数的所有局部变量,命名参数,参数集合和this。此对象会被推入作用域链的最前端。 当执行环境被销毁后,"活动对象"也会随之被销毁。
js执行期上下文
执行上下文就是JavaScript 在被解析和运行时环境的抽象概念,JavaScript 运行任何代码都是在执行上下文环境中运行的,执行上下文包括三个周期:创建——运行——销毁,重点说一下创建环节。
创建环节(函数被调用,但未未被执行)会执行三件事情
- 创建变量对象,首先初始化函数的arguments对象,提升函数声明和变量声明,从近到远查找函数运行所需要的变量。
- 创建作用域链,作用域就是一个独立的地盘,让变量不会相互干扰,当前作用域没有定义的变量,这成为 自由变量。自由变量会向上一直寻找,要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,如果最终没有就为undefined。这种层层之间就构成了作用域链。
- 确定this指向,this、apply、call的指向
function test(arg){ // 1. 形参 arg 是 "hi" // 2. 因为函数声明比变量声明优先级高,所以此时 arg 是 function console.log(arg); var arg = 'hello'; // 3.var arg 变量声明被覆盖, arg = 'word'被执行 function arg(){ console.log('hello world') } var arg = 'word'; console.log(arg); } test('hi');
可以看下上面这个例子,函数内函数声明比变量声明优先,所以arg被覆盖,并且被提升,所以第一次打印不会报错,打出了arg函数,后面变量被覆盖成为hello。
函数执行多了就会有多个执行上下文,那么怎么管理这些执行上下文呢?
JavaScript 引擎创建了执行栈来管理执行上下文,可以把执行栈认为成一个储存函数调用的栈结构,遵循先进后出的原则。
//执行上下文特点 1.单线程,在主进程上运行 2.同步执行,从上往下按顺序执行 3.全局上下文只有一个,浏览器关闭时会被弹出栈 4.函数的执行上下文没有数目限制 5.函数每被调用一次,都会产生一个新的执行上下文环境 //执行上下文栈 执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。
当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。 其实这是一个压栈出栈的过程——执行上下文栈。 压栈出栈过程 var // 1.进入全局上下文环境 a = 10, fn, bar = function(x){ var b = 20; fn(x + b); // 3.进入fn上下文环境 } fn = function(y){ var c = 20; console.log(y + c); } bar(5); // 2.进入bar上下文环境 执行上下文生命周期 如图所示,执行上下文共分3个阶段,分别是: 1.创建阶段 (1).生成变量对象 (2).建立作用域链 (3).确定 this 指向 2.执行阶段 (1).变量赋值 (2).函数引用 (3).执行其他代码 3.销毁阶段 执行完毕出栈,等待回收被销毁 所以上面小例子等同于: function fn(){ var a; console.log(a); a = 1; } fn();
24、var、let、const的区别
let和const是es6新增的两个变量声明关键字,与var的不同点在于:
(1)let和const都是块级作用域,在{}内有效,这点在for循环中非常有用,只在循环体内有效。var为函数作用域。
(2)使用let和const声明的变量,不存在变量提升,必须先声明再使用。使用var声明的变量可以先使用再定义,而 var 定义的变量会提升。
(3)相同作用域中,let 和 const 不允许重复声明,var 允许重复声明。
const特殊要点:
(1)const顾名思义常量,但这个常量与高级语言的常量有所不同,这里的常量指的是在定义就确定其值,并且这个值只读,不可以修改;
高级语言的常量更狭隘一点;
(2)使用const声明的变量,一旦声明需要立即初始化,只声明的变量无法用const,const a; 编译会报错;并且一旦初始化,就不能改变。
(3)const用于定义一个复合型对象或者数组时,只是对对象或者数组本身不可赋值,但依然可以为对象添加属性或者为数组push元素。
25、setTimeout()和setInterval()的区别
setTimeout()和setInterval()经常被用来处理延时和定时任务。 setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式,而setInterval()则可以在每隔指定的毫秒数循环调用函数或表达式,
直到clearInterval把它清除。 setTimeout()只执行一次,而setInterval可以多次调用。 尽量不用setInterval() //原因一、setInterval()无视代码错误 //原因二、setInterval无视网络延迟 //原因三、setInterval不保证执行 与setTimeout不同,你并不能保证到了时间间隔,代码就准能执行。如果你调用的函数需要花很长时间才能完成,那某些调用会被直接忽略
手写实现图片懒加载
var num = document.getElementsByTagName('img').length; # 获取img 标签的数量 var img = document.getElementsByTagName("img"); # 找到所有的img标签 数组集合[ , , ] var n = 0; # 存储图片加载到的位置,避免每次都从第一张图片开始遍历 lazyload(); # 页面载入完毕,调用lazyload()方法 加载可是区域内的图片 window.onscroll = lazyload; # 滑动事件,把 lazyload方法 交给window.onscroll,当页面滑动会执行 lazyload方法 function lazyload() { //监听页面滚动事件 var seeHeight = document.documentElement.clientHeight; //可见区域高度 var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度 for (var i = n; i < num; i++) { # img[i].offsetTop 距离页面顶部的高度 if (img[i].offsetTop < seeHeight + scrollTop) { # 进入可视区域内 if (img[i].getAttribute("src") == "") { img[i].src = img[i].getAttribute("data-src"); } n = i + 1; } }
26、js如何控制一次加载一张图片
onloading……
27、js手写Ajax的post和get方法?
//get function get() { //创建XMLHttpRequest let xhr = new XMLHttpRequest(); //监听响应 xhr.onreadystatechange = function () { if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)) { console.log(xhr.responseText); } }; xhr.open("GET","your url",true); xhr.send(); } //post function post () { //请求参数、url、创建XMLHttpRequest let data = 'name=tom&age=18', url = 'your url', xhr = new XMLHttpRequest(); xhr.open('post', url); //设置header xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.send(data); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && ( xhr.status === 200 || xhr.status === 304 )){ console.log(xhr.responseText); } } }
27、取指定url,将参数转为json对象返回
function parseQueryString(str){ var arr = [], length = 0, res = {}, si=str.indexOf("?"); str=str.substring(si+1); console.log(str); arr = str.split('&'); length = arr.length; for(var i=0; i){ res[arr[i].split('=')[0]] = arr[i].split('=')[1]; } return res; }
28、js脚本加载是全加载完再执行还是加载一个执行一个?
29、怎么判断一个图片的大小?
//在HTML 5中,新增加了两个用来判断图片的宽度和高度的属性,分别为naturalWidth 和 naturalHeight属性,
30、js的设计模式
https://www.cnblogs.com/tugenhua0707/p/5198407.html#_labe0
工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。 简单的工厂模式可以理解为解决多个相似的问题;这也是她的优点;比如如下代码: function CreatePerson(name,age,sex) { var obj = new Object(); obj.name = name; obj.age = age; obj.sex = sex; obj.sayName = function(){ return this.name; } return obj; } var p1 = new CreatePerson("longen",'28','男'); var p2 = new CreatePerson("tugenhua",'27','女'); console.log(p1.name); // longen console.log(p1.age); // 28 console.log(p1.sex); // 男 console.log(p1.sayName()); // longen console.log(p2.name); // tugenhua console.log(p2.age); // 27 console.log(p2.sex); // 女 console.log(p2.sayName()); // tugenhua // 返回都是object 无法识别对象的类型 不知道他们是哪个对象的实列 console.log(typeof p1); // object console.log(typeof p2); // object console.log(p1 instanceof Object); // true 如上代码:函数CreatePerson能接受三个参数name,age,sex等参数,可以无数次调用这个函数,每次返回都会包含三个属性和一个方法的对象。 工厂模式是为了解决多个类似对象声明的问题;也就是为了解决实列化对象产生重复的问题。 优点:能解决多个相似的问题。 缺点:不能知道对象识别的问题(对象的类型不知道)。
31、es6了解哪些?
ES6新增的一些特性: 1)let声明变量和const声明常量,两个都有块级作用域 ES5中是没有块级作用域的,并且var有变量提升,在let中,使用的变量一定要进行声明 2)箭头函数 ES6中的函数定义不再使用关键字function(),而是利用了()=>来进行定义 3)模板字符串 模板字符串是增强版的字符串,用反引号(`)标识,可以当作普通字符串使用,也可以用来定义多行字符串 4)解构赋值 ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值 5)for of循环 for...of循环可以遍历数组、Set和Map结构、某些类似数组的对象、对象,以及字符串 6)import、export导入导出 ES6标准中,Js原生支持模块(module)。将JS代码分割成不同功能的小块进行模块化,将不同功能的代码分别写在不同文件中,
各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用 7)set数据结构 Set数据结构,类似数组。所有的数据都是唯一的,没有重复的值。它本身是一个构造函数 8)... 展开运算符 可以将数组或对象里面的值展开;还可以将多个值收集为一个变量 9)修饰器 @ decorator是一个函数,用来修改类甚至于是方法的行为。修饰器本质就是编译时执行的函数 10)class 类的继承 ES6中不再像ES5一样使用原型链实现继承,而是引入Class这个概念 11)async、await 使用 async/await, 搭配promise,可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成 12)promise Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理、强大 13)Symbol Symbol是一种基本类型。Symbol 通过调用symbol函数产生,它接收一个可选的名字参数,该函数返回的symbol是唯一的 14)Proxy代理 使用代理(Proxy)监听对象的操作,然后可以做一些相应事情
32、箭头函数说一下
箭头函数省去了function关键字,采用箭头=>来定义函数。函数的参数放在=>前面的括号中,函数体跟在=>后的花括号中。
关于箭头函数的参数:
① 如果箭头函数没有参数,直接写一个空括号即可。
② 如果箭头函数的参数只有一个,也可以省去包裹参数的括号。
③ 如果箭头函数有多个参数,将参数依次用逗号(,)分隔,包裹在括号中即可。
3、使用箭头函数应注意什么?
(1)用了箭头函数,this就不是指向window,而是父级(指向是可变的)
(2)不能够使用arguments对象
(3)不能用作构造函数,这就是说不能够使用new命令,否则会抛出一个错误
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数
33、axios返回的是什么?
//返回的是promise
34、promise是什么、怎么用、实现原理?
Promise是什么?
Promise是一种用于解决异步问题的思路、方案或者对象方式。
为什么要用promise?
1、回调地狱 + 异步同步事件调用顺序带来的双重伤害
当我们的多个异步逻辑存在先后依赖的时候,引起回调地狱。为什么叫回调地狱呢?因为这种写法会有以下几个问题
- 代码嵌套过多,横向扩展,可读性差。
- 外部无法捕获回调函数内部的错误信息。
- 回调函数的执行次数无法得到保证。
Promise怎么用? Promise是一个对象,所以先用new的方式创建一个,然后给它传一个函数作为参数,这个函数有两个参数,一个叫reolve,另一个叫reject、 紧接着,就用then来进行调用 Promise原理 在Promise内部,有一个状态管理器的存在,有三种状态: pending、fulfilled、rejected (1) promise初始化状态为pending (2) 当前调用resolve(成功), 会由pending => fulfilled (3) 当调用reject(失败), 会由pending => rejected
35、Promise构造函数是同步还是异步执行,then中的方法呢 ?promise如何实现then处理 ?
Promise的构造函数是同步执行的。then 中的方法是异步执行的。
36、set和map的区别?
// ES6数组去重 // 方法1 Map // Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现 // ES6中的Map类型是一种储存着许多键值对的有序列表,其中的键名和对应的值支持所有的数据类型。 // 提供三个新的方法 —— entries(),keys()和values() —— // 用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历, // 唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。 // 如果要向Map集合中添加新的元素,可以调用set()方法并分别传入键名和对应值作为两个参数; // 如果要从集合中获取信息,可以调用get()方法 function unique(arr) { //定义常量 res,值为一个Map对象实例 const res = new Map(); //返回arr数组过滤后的结果,结果为一个数组 //过滤条件是,如果res中没有某个键,就设置这个键的值为1 return arr.filter((a) => !res.has(a) && res.set(a, 1)) } // 方法2 Set // Set结构是类似于数组结构,但是成员都是不重复的值,通过Set集合可以快速访问其中的数据,更有效地追踪各种离散值 // 缺点是没办法像数组一样通过下标取值的方法. // 可以使用Set实例对象的keys(),values(),entries()方法进行遍历。 // 由于Set的键名和键值是同一个值,它的每一个元素的key和value是相同的, // 所有keys()和values()的返回值是相同的,entries()返回的元素中的key和value是相同的。 //调用new Set()创建Set集合,调用add()方法向集合中添加元素,访问集合的size属性可以获取集合中目前的元素数量 function unique(arr2) { //通过Set对象,对数组去重,结果又返回一个Set对象 //通过from方法,将Set对象转为数组 return Array.from(new Set(arr2)) } console.log(unique(arr2)); // 无需数组取第二大的数 var arr=[5,2,10,8,0,4,7,11,9,1]; function array2(){ var temp,min; for(var i=0;i){ min=i; for(var j=i+1;j ){ if(arr[j]>arr[i]){ temp= arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } alert(arr[1]); } array2();
36、axios是什么?
//axios 主要是用来向后台发送请求。支持promise //axios 支持并发请求,可以同时请求多个接口 //axios 提供了拦截器。 //axios 可以防止 跨站请求伪造。也就是钓鱼网站 //axios 拦截器: 分为 request 请求拦截和 response 响应拦截 request 请求拦截:发送请求前统一处理。例如:设置请求头 headers response 相应拦截:是根据响应的代码来进行下一步的操作。例如:由于当前的 token 过期,接口返回 401 未授权。那么我们就要跳转到登陆界面
38、Vue如何与后端交互
//1. 通过axios实现数据请求 vue.js默认没有提供ajax功能的。 //所以使用vue的时候,一般都会使用axios的插件来实现ajax与后端服务器的数据交互。 注意,axios本质上就是javascript的ajax封装,所以会被同源策略限制。 // 发送get请求 // 参数1: 必填,字符串,请求的数据接口的url地址,例如请求地址:http://www.baidu.com?id=200 // 参数2:可选,json对象,要提供给数据接口的参数 // 参数3:可选,json对象,请求头信息 axios.get('服务器的资源地址',{ // http://www.baidu.com params:{ 参数名:'参数值', // id: 200, } }).then(function (response) { // 请求成功以后的回调函数 console.log("请求成功"); console.log(response); }).catch(function (error) { // 请求失败以后的回调函数 console.log("请求失败"); console.log(error.response); }); // 发送post请求,参数和使用和axios.get()一样。 // 参数1: 必填,字符串,请求的数据接口的url地址 // 参数2:必填,json对象,要提供给数据接口的参数,如果没有参数,则必须使用{} // 参数3:可选,json对象,请求头信息 axios.post('服务器的资源地址',{ username: 'xiaoming', password: '123456' },{ responseData:"json", }) .then(function (response) { // 请求成功以后的回调函数 console.log(response); }) .catch(function (error) { // 请求失败以后的回调函数 console.log(error); }); // b'firstName=Fred&lastName=Flintstone'
39、vue-if和Vue-show有什么区别
v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。 所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
40、Vue的数据驱动
数据驱动,既视图是由数据生成的,我们要修改试图不需要修改dom,而是只需要修改数据,相比于传统的修改dom,大大的减少了代码量。
特别是当交互特别复杂的时候,只关注数据的修改会让代码的逻辑变得非常清晰,因为dom是对数据的映射,所有逻辑都是对数据的修改,而不用触碰dom,
这样特别有利于代码的维护。数据驱动的原理是依赖收集的观测机制,vue在实例化过程中,会遍历传给实例化对象的data选项 ,遍历其所有的属性,
并使用Object.definedPropety给这些属性添加set和get方法。同时每一个实例对象都有一个watcher实例对象。
它会在模板编译过程中用get方法去访问data属性,watcher此时就会报用到的data属性记为依赖,这样就建立了视图和数据之间的联系。
当之后我们渲染视图的依赖 发生改变(既数据的set方法被调用)的时候。watcher会对比前后两个数值是否发生变化,然后通知视图确定是否进行渲染,这样就实现了所谓的数据对视图的驱动。
41、Vue的组件系统
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。
组件需要注册后才可以使用,注册有全局注册和局部注册两种方式。
组件的创建是新创建一个vue文件,template写html代码,script写js代码,style写css代码。
42、请说下封装 vue 组件的过程?
首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模快,解决了我们传统项目开发:效率低、难维护、复用性等问题。
然后,使用Vue.extend方法创建一个组件,然后使用Vue.component方法注册组件。子组件需要数据,可以在props中接受定义。
而子组件修改好数据后,想把数据递给父组件。可以采用emit方法。
43、Vue组件传值?
//父子通信: //父传子 首先在子组件的props中定义好数据名,父组件template的子组件标签中用v-bind绑定数据名=值,这个值就能在子组件中通过this获取到了,
如果要做成实时触发的可以在子组件中用watch对props的值进行监控,并且实时赋值给本组件的变量。 //子传父 在子组件的方法中用$emit方法通过事件名,事件值的方式传出,父组件在template的子组件标签中用@子组件中设置的事件名=方法,
在这个方法的返回值中就可以拿到子组件传出来的值。 //兄弟组件通信: 通常用vuex,首先在state中配置好数据名,再设置set方法和get方法,在获取或者修改值的时候,用get和set方法操作就行了。
还有一种evenBus,是通过同时导入同一个js文件做中转的方式。
44、EventBus如何实现
//eventBus 嗯 ,就叫一个事件公共汽车吧。每个人把需要共享给别人的物品就放在这个车上,谁需要了就可以去拿,
这样子是不是很方便,每个人都可以访问到,每个人也可以往这个车子上放东西。 下面开始这个过程,一共就四步。 //第一步就是先把公交车造出来。先创建eventBus.js 文件。 /** * 某某页面 * @author: leon * @create: 2018-05-21 10:01 */ import Vue from 'vue'; const bus = new Vue(); export default { /** * 注册全局事件 * @param eventName 事件名称 * @param handler 事件处理函数 * @param scope vm对象,一般传this 建议必须要传(页面的this),自动销毁功能 * @param once 是否单次注册 */ on(eventName, handler, scope = null, once = false) { if (once) { bus.$once(eventName, handler); return; } bus.$on(eventName, handler); if (scope) { let originalDestroy = scope.$destroy; scope.$destroy = function () { bus.$off(eventName, handler); originalDestroy.call(this); } } }, /** * 触发事件 * @param eventName 要触发的事件名称 * @param data 事件对象 */ emit(eventName, data = {}) { bus.$emit(eventName, data); } }; //第二步就是让这个公共汽车上路。开车了。在全局引入 import eventBus from './eventBus' Vue.prototype.$eventBus = eventBus; //第三步就是放东西。传值这里通过emit这个方法进行传递。一般是放在函数里调用。 getNum(){ this.$eventBus.emit("haha","我是值") } //第四步就是拿下来咯,一般写在mounted里面,这样可以避免一个多次触发的问题。当然可以自己写在函数里试一试。 mounted() { this.$eventBus.on( "haha", function(data) { console.log(data); }, this ); },
45、Vue的双向绑定的原理,具体实现是什么?
vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,
通过Object.defineProperty()来劫持各个属性的setter,getter,
在数据变动时发布消息给订阅者,触发相应监听回调。
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,
用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,
在属性被访问和修改时通知变化。
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
具体步骤: 第一步:需要 Observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到数据变化。 第二步:Compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图 第三步:Watcher 订阅者是 Observe 和 Compile 之间通信的桥梁,主要的事情是: 1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个update() 3、待属性变动dep.notify()通知时,能调用自身的 update() 方法,并触发 Compile 中绑定回调,则功成身退。 第四步:MVVM作为数据绑定的入口,整合 Observe、Compile 和 Watcher 三者,通过 Observe 来监听自己的 Model 数据变化。 通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observe 和 Compile 之间的通信桥梁; 达到数据变化 -> 视图更新; 视图交互(input) -> 数据 Model 变更的双向绑定效果。
46、手动实现双向数据绑定
const data = {}; const input = document.getElementById('input'); Object.defineProperty(data, 'text', { set(value) { input.value = value; this.value = value; } }); input.onchange = function(e) { data.text = e.target.value; }
47、实现双向绑定 Proxy 与 Object.defineProperty 相比优劣如何?具体如何使用?
Object.definedProperty 的作用是劫持一个对象的属性,劫持属性的getter和setter方法,在对象的属性发生变化时进行特定的操作。而 Proxy 劫持的是整个对象。 Proxy 会返回一个代理对象,我们只需要操作新对象即可,而 Object.defineProperty 只能遍历对象属性直接修改。 Object.definedProperty 不支持数组,更准确的说是不支持数组的各种API,因为如果仅仅考虑arry[i] = value 这种情况,是可以劫持的,
但是这种劫持意义不大。而 Proxy 可以支持数组的各种API。 尽管 Object.defineProperty 有诸多缺陷,但是其兼容性要好于 Proxy.
48、vue-router原理?
原理核心就是 更新视图但不重新请求页面。 vue-router实现单页面路由跳转,提供了三种方式:hash模式、history模式、abstract模式,根据mode参数来决定采用哪一种方式。 路由模式 vue-router 提供了三种运行模式: ● hash: 使用 URL hash 值来作路由。默认模式。 ● history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。 ● abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端 //3、Hash模式 hash即浏览器url中#后面的内容,包含#。hash是URL中的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会加载相应位置的内容,不会重新加载页面。 也就是说 即#是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中,不包含#。 每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。 所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。 //2、History模式 HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面; 由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'",
这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。 有时,history模式下也会出问题: eg: hash模式下:xxx.com/#/id=5 请求地址为 xxx.com,没有问题。 history模式下:xxx.com/id=5 请求地址为 xxx.com/id=5,如果后端没有对应的路由处理,就会返回404错误; 为了应对这种情况,需要后台配置支持: 在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。 //3、abstract模式 abstract模式是使用一个不依赖于浏览器的浏览历史虚拟管理后端。 根据平台差异可以看出,在 Weex 环境中只支持使用 abstract 模式。 不过,vue-router 自身会对环境做校验,
如果发现没有浏览器的 API,vue-router 会自动强制进入 abstract 模式,所以 在使用 vue-router 时只要不写 mode 配置即可,
默认会在浏览器环境中使用 hash 模式,在移动端原生环境中使用 abstract 模式。 (当然,你也可以明确指定在所有情况下都使用 abstract 模式)。
49、Vue-router有哪几种钩子?
三种
全局导航钩子(跳转前进行判断拦截)
router.beforeEach(to, from, next),
router.beforeResolve(to, from, next),
router.afterEach(to, from ,next)
组件内钩子
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
单独路由独享组件
beforeEnter
50、如何解决异步回调地狱?
异步回调地狱是指代码横向增长(比如在Promise对象里面循环嵌套了多个Promise对象)而不是竖向增长,
这样的代码就会显得很复杂而且逻辑会变得混乱,可怕的是会写出强耦合的异步代码,后期修改的时候就嘿嘿嘿了。 解决方法: //1.用Promise的 .then 写成链式结构实现竖向增长 //2.使用 await //3.使用Generator协程
await和async
https://www.cnblogs.com/CandyManPing/p/9384104.html
async 告诉程序这是一个异步,awiat 会暂停执行async中的代码,等待await 表达式后面的结果,跳过async 函数,继续执行后面代码
async 函数会返回一个Promise 对象,那么当 async 函数返回一个值时,
Promise 的 resolve 方法会负责传递这个值;当 async 函数抛出异常时,Promise 的 reject 方法也会传递这个异常值
await 操作符用于等待一个Promise 对象,并且返回 Promise 对象的处理结果(成功把resolve 函数参数作为await 表达式的值),
如果等待的不是 Promise 对象,则用 Promise.resolve(xx) 转化
51、vue的生命周期
beforeCreate(创建前) 在数据观测和初始化事件还未开始 created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来 beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。
实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。
实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。 updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。
然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。 beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。 destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。 1.什么是vue生命周期? 答: Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。 2.vue生命周期的作用是什么? 答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。 3.vue生命周期总共有几个阶段? 答:它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。 4.第一次页面加载会触发哪几个钩子? 答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。 5.DOM 渲染在 哪个周期中就已经完成? 答:DOM 渲染在 mounted 中就已经完成了。
52、beforecreate都干了些什么呢
从beforeCreate到created,执行了数据观测 (data observer) 和 event/watcher 事件配置。
53、vue中的create和mount都干了什么?
created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。 beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。 mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
54、vue中computed和watch的区别,分别什么时候用?
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),
限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
55、vue computed的实现原理和缓存原理?
第一步:创建一个computedWathcers 空对象, 对 computed 对象遍历,获取计算属性每一个 userDef(自定义的函数或对象),
然后尝试获取 userDef 的getter,并且为每一个 getter 添加一个watcher
第二步:判断遍历 computed 对象的key,是否已经存在 data 和 props 所占用,存在则发出警告,
不存在就调用 defineComputed 函数,给对应的key添加getter 和 setter
第三步:在调用 defineComputed 函数,会进行依赖收集 computedWatcher ,通过computedWatcher来进行派发通知,更新视图
第四步:缓存就是在获取 getter 数据的,判断是否值相等,相等的话就直接返回,不再进行更新视图
56、手动实现Vue的computed
初始化 data, 使用 Object.defineProperty 把这些属性全部转为 getter/setter。
初始化 computed, 遍历 computed 里的每个属性,每个 computed 属性都是一个 watch 实例。每个属性提供的函数作为属性的 getter,使用 Object.defineProperty 转化。
Object.defineProperty getter 依赖收集。用于依赖发生变化时,触发属性重新计算。
若出现当前 computed 计算属性嵌套其他 computed 计算属性时,先进行其他的依赖收集。
57、keep-alive的作用
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
-
- 一般结合路由和动态组件一起使用,用于缓存组件;
- 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
- 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
.then() .catch()和 .then(data(),err())的区别是什么?
异常,then的第二个参数捕获不到
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
then的第二个参数处理不了的。
Vue组件样式覆盖问题
//vue样式加scoped后不能覆盖组件的原有样式解决方法 为了vue页面样式模块化,不对全局造成污染,建议每个页面的style标签加上scoped,表示他的样式只属于当前的页面,父组件的样式不会泄漏到子组件中。
但是scoped也会造成一些额外的负担,如无法覆盖原有组件的样式。 可以加 /deep/ 。 深度作用选择器 /deep/ or >>> 如果希望scoped样式中的选择器“深入”,即影响子组件 例子: /deep/ .el-table .red-row { background: #ffc2c2; } or .el-table >>> .red-row { background: #ffc2c2; }
打包工具git
//有时候,进行了错误的提交,但是还没有push到远程分支,想要撤销本次提交,可以使用git reset –-soft/hard命令。 //1、二者区别: git reset –-soft:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可; git reset -–hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容,撤销的commit中所包含的更改被冲掉; //说说 fetch 和 pull 的不同: fetch 只能更新远程仓库的代码为最新的,本地仓库的代码还未被更新,我们需要通过 git merge origin/master 来合并这两个版本,你可以把它理解为合并分支一样的。 pull 操作是将本地仓库和远程仓库(本地的)更新到远程的最新版本。 如果想要更加可控一点的话推荐使用fetch + merge。
58、什么是虚拟dom?
https://www.cnblogs.com/duanlibo/p/10969660.html
//数据对象 虚拟DOM可以理解为我们根据页面的真实的DOM结构抽象出来的一种数据结构,一个层级比较复杂的对象,和真实的DOM一一映射。
我们可以通过调用一个渲染函数比如render,将数据对象作为参数传入,便可得到一个真实的能在页面显示的DOM。 //diff算法 DOM的更新依赖于数据的改变。我们并不希望由于数据的改变而需要整个DOM结构的重新渲染。 借助diff算法,通过对比前后虚拟DOM的差异,进行有针对性的打补丁渲染。算法并不简单,因为考虑很多种情况,自行脑补。
其中需要用到递归,父子节点判断的轮回。 // 简化版,实际远复杂的多 function patchElement(parent, newVNode, oldVNode, index = 0) { if(!oldVNode) { parent.appendChild(newVNode.render()) } else if(!newVNode) { parent.removeChild(parent.childNodes[index]) } else if(newVNode.tag !== oldVNode.tag || newVNode.text !== oldVNode.text) { parent.replaceChild(newVNode.render(), parent.childNodes[index]) } else { for(let i = 0; i < newVNode.children.length || i < oldVNode.children.length; i++) { patchElement(parent.childNodes[index], newVNode.children[i], oldVNode.children[i], i) } } } let vNodes1 = v('div', [ v('p', [ v('span', [ v('#text', 'xiedaimala.com') ] ) ] ), v('span', [ v('#text', 'jirengu.com') ]) ] ) let vNodes2 = v('div', [ v('p', [ v('span', [ v('#text', 'xiedaimala.com') ] ) ] ), v('span', [ v('#text', 'jirengu.coms'), v('#text', 'ruoyu') ]) ] ) const root = document.querySelector('#root') patchElement(root, vNodes1)
59、说一下event lop?
首先,js是单线程的,主要的任务是处理用户的交互,而用户的交互无非就是响应DOM的增删改,
使用事件队列的形式,一次事件循环只处理一个事件响应,使得脚本执行相对连续,所以有了事件队列,用来储存待执行的事件,那么事件队列的事件从哪里被push进来的呢。
那就是另外一个线程叫事件触发线程做的事情了,他的作用主要是在定时触发器线程、异步HTTP请求线程满足特定条件下的回调函数push到事件队列中,
等待js引擎空闲的时候去执行,当然js引擎执行过程中有优先级之分,首先js引擎在一次事件循环中,会先执行js线程的主任务,
然后会去查找是否有微任务microtask(promise),如果有那就优先执行微任务,如果没有,在去查找宏任务macrotask(setTimeout、setInterval)进行执行。
60、ES6模块和CommonJS模块的差异?
ES6模块在编译时,就能确定模块的依赖关系,以及输入和输出的变量。 CommonJS 模块,运行时加载。 ES6 模块自动采用严格模式,无论模块头部是否写了 "use strict"; require 可以做动态加载,import 语句做不到,import 语句必须位于顶层作用域中。 ES6 模块中顶层的 this 指向 undefined,CommonJS 模块的顶层 this 指向当前模块。 CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
61、手写实现将json数组命名转为驼峰命名
const testData1 = { a_bbb: 123, a_g: [0, 1, 2, 3, 4], a_d: { s: 2, s_d: 's_d' }, a_f: [1, 2, 3, { a_g: 5 }], a_d_s: 1 } const testData2 = [ { a_g: { s: 2, s_d: 's_d' }, a_d_s: 'a_d' } ] /** * 将一个json数据的所有key从下划线改为驼峰 * * @param {object | array} value 待处理对象或数组 * @returns {object | array} 处理后的对象或数组 */ function mapKeysToCamelCase(data) { let newObj = {}; if(typeof data!=='object'){ return data; }else{ if(Array.isArray(data)){ //是数组,判断每个元素是否是对象或数组,通过递归 for(let i = 0;i){ data[i] = mapKeysToCamelCase(data[i]); } return data; }else{ //对象 for(let item in data){ let newData = item.toString().split("_"); if(newData.length == 1){//代表不包含'_'符号 newObj[newData[0]] = mapKeysToCamelCase(data[item]); }else{ let str = newData[0]; for(let i = 1;i ){ str += newData[i].toUpperCase(); } newObj[str] = mapKeysToCamelCase(data[item]); } } } } return newObj; } console.log(mapKeysToCamelCase(testData1)) console.log(mapKeysToCamelCase(testData2))
62、手写实现一个new操作符
function New(func) { var res = {}; if (func.prototype !== null) { res.__proto__ = func.prototype; } var ret = func.apply(res, Array.prototype.slice.call(arguments, 1)); if ((typeof ret === "object" || typeof ret === "function") && ret !== null) { return; ret; } return; res; } var obj = New(A, 1, 2); // equals to var obj = new A(1, 2);
63、手写实现call
//传递参数从一个数组变成逐个传参了,不用...扩展运算符的也可以用arguments代替 Function.prototype.myCall = function (context, ...args) { //这里默认不传就是给window,也可以用es6给参数设置默认参数 context = context || window args = args ? args : [] //给context新增一个独一无二的属性以免覆盖原有属性 const key = Symbol() context[key] = this //通过隐式绑定的方式调用函数 const result = context[key](...args) //删除添加的属性 delete context[key] //返回函数调用的返回值 return result }
64、手写实现apply
Function.prototype.myApply = function (context, args) { //这里默认不传就是给window,也可以用es6给参数设置默认参数 context = context || window args = args ? args : [] //给context新增一个独一无二的属性以免覆盖原有属性 const key = Symbol() context[key] = this //通过隐式绑定的方式调用函数 const result = context[key](...args) //删除添加的属性 delete context[key] //返回函数调用的返回值 return result }
65、手写实现一个Function.bind()方法
Function.prototype.bind = function(context) { //返回一个绑定this的函数,我们需要在此保存this let self = this // 可以支持柯里化传参,保存参数 let arg = [...arguments].slice(1) // 返回一个函数 return function() { //同样因为支持柯里化形式传参我们需要再次获取存储参数 let newArg = [...arguments] console.log(newArg) // 返回函数绑定this,传入两次保存的参数 //考虑返回函数有返回值做了return return self.apply(context, arg.concat(newArg)) } } // 搞定测试 let fn = Person.say.bind(Person1) fn() fn(18)
66、手写实现一个继承
function Parent(name) { this.name = name; } Parent.prototype.sayName = function() { console.log('parent name:', this.name); } function Child(name, parentName) { Parent.call(this, parentName); this.name = name; } function create(proto) { function F() {} F.prototype = proto; return new F(); } Child.prototype = create(Parent.prototype); Child.prototype.sayName = function() { console.log('child name:', this.name); } Child.prototype.constructor = Child; var parent = new Parent('汪某'); parent.sayName();// parent name: 汪某 var child = new Child('son', '汪某');
67、手写实现promise
//简易版promise // 简易版本的promise // 第一步: 列出三大块 this.then resolve/reject fn(resolve,reject) // 第二步: this.then负责注册所有的函数 resolve/reject负责执行所有的函数 // 第三步: 在resolve/reject里面要加上setTimeout 防止还没进行then注册 就直接执行resolve了 // 第四步: resolve/reject里面要返回this 这样就可以链式调用了 // 第五步: 三个状态的管理 pending fulfilled rejected // *****promise的链式调用 在then里面return一个promise 这样才能then里面加上异步函数 // 加上了catch function PromiseM(fn) { var value = null; var callbacks = []; //加入状态 为了解决在Promise异步操作成功之后调用的then注册的回调不会执行的问题 var state = 'pending'; var _this = this; //注册所有的回调函数 this.then = function (fulfilled, rejected) { //如果想链式promise 那就要在这边return一个new Promise return new PromiseM(function (resolv, rejec) { //异常处理 try { if (state == 'pending') { callbacks.push(fulfilled); //实现链式调用 return; } if (state == 'fulfilled') { var data = fulfilled(value); //为了能让两个promise连接起来 resolv(data); return; } if (state == 'rejected') { var data = rejected(value); //为了能让两个promise连接起来 resolv(data); return; } } catch (e) { _this.catch(e); } }); } //执行所有的回调函数 function resolve(valueNew) { value = valueNew; state = 'fulfilled'; execute(); } //执行所有的回调函数 function reject(valueNew) { value = valueNew; state = 'rejected'; execute(); } function execute() { //加入延时机制 防止promise里面有同步函数 导致resolve先执行 then还没注册上函数 setTimeout(function () { callbacks.forEach(function (cb) { value = cb(value); }); }, 0); } this.catch = function (e) { console.log(JSON.stringify(e)); } //经典 实现异步回调 fn(resolve, reject); }
/** * Promise 实现 遵循promise/A+规范 * Promise/A+规范译文: * https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4 */ // promise 三个状态 const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; function Promise(excutor) { let that = this; // 缓存当前promise实例对象 that.status = PENDING; // 初始状态 that.value = undefined; // fulfilled状态时 返回的信息 that.reason = undefined; // rejected状态时 拒绝的原因 that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数 that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数 function resolve(value) { // value成功态时接收的终值 if(value instanceof Promise) { return value.then(resolve, reject); } // 为什么resolve 加setTimeout? // 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行. // 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,
且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。 setTimeout(() => { // 调用resolve 回调对应onFulfilled函数 if (that.status === PENDING) { // 只能由pedning状态 => fulfilled状态 (避免调用多次resolve reject) that.status = FULFILLED; that.value = value; that.onFulfilledCallbacks.forEach(cb => cb(that.value)); } }); } function reject(reason) { // reason失败态时接收的拒因 setTimeout(() => { // 调用reject 回调对应onRejected函数 if (that.status === PENDING) { // 只能由pedning状态 => rejected状态 (避免调用多次resolve reject) that.status = REJECTED; that.reason = reason; that.onRejectedCallbacks.forEach(cb => cb(that.reason)); } }); } // 捕获在excutor执行器中抛出的异常 // new Promise((resolve, reject) => { // throw new Error('error in excutor') // }) try { excutor(resolve, reject); } catch (e) { reject(e); } } /** * resolve中的值几种情况: * 1.普通值 * 2.promise对象 * 3.thenable对象/函数 */ /** * 对resolve 进行改造增强 针对resolve中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { // 如果从onFulfilled中返回的x 就是promise2 就会导致循环引用报错 return reject(new TypeError('循环引用')); } let called = false; // 避免多次调用 // 如果x是一个promise对象 (该判断和下面 判断是不是thenable对象重复 所以可有可无) if (x instanceof Promise) { // 获得它的终值 继续resolve if (x.status === PENDING) { // 如果为等待态需等待直至 x 被执行或拒绝 并解析y值 x.then(y => { resolvePromise(promise2, y, resolve, reject); }, reason => { reject(reason); }); } else { // 如果 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promise x.then(resolve, reject); } // 如果 x 为对象或者函数 } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) { try { // 是否是thenable对象(具有then方法的对象/函数) let then = x.then; if (typeof then === 'function') { then.call(x, y => { if(called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, reason => { if(called) return; called = true; reject(reason); }) } else { // 说明是一个普通对象/函数 resolve(x); } } catch(e) { if(called) return; called = true; reject(e); } } else { resolve(x); } } /** * [注册fulfilled状态/rejected状态对应的回调函数] * @param {function} onFulfilled fulfilled状态时 执行的函数 * @param {function} onRejected rejected状态时 执行的函数 * @return {function} newPromsie 返回一个新的promise对象 */ Promise.prototype.then = function(onFulfilled, onRejected) { const that = this; let newPromise; // 处理参数默认值 保证参数后续能够继续执行 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value; onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason; }; // then里面的FULFILLED/REJECTED状态时 为什么要加setTimeout ? // 原因: // 其一 2.2.4规范 要确保 onFulfilled 和 onRejected 方法异步执行(且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行) 所以要在resolve里加上setTimeout // 其二 2.2.6规范 对于一个promise,它的then方法可以调用多次.(当在其他程序中多次调用同一个promise的then时
由于之前状态已经为FULFILLED/REJECTED状态,则会走的下面逻辑),所以要确保为FULFILLED/REJECTED状态后 也要异步执行onFulfilled/onRejected // 其二 2.2.6规范 也是resolve函数里加setTimeout的原因 // 总之都是 让then方法异步执行 也就是确保onFulfilled/onRejected异步执行 // 如下面这种情景 多次调用p1.then // p1.then((value) => { // 此时p1.status 由pedding状态 => fulfilled状态 // console.log(value); // resolve // // console.log(p1.status); // fulfilled // p1.then(value => { // 再次p1.then 这时已经为fulfilled状态 走的是fulfilled状态判断里的逻辑 所以我们也要确保判断里面onFuilled异步执行 // console.log(value); // 'resolve' // }); // console.log('当前执行栈中同步代码'); // }) // console.log('全局执行栈中同步代码'); // if (that.status === FULFILLED) { // 成功态 return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try{ let x = onFulfilled(that.value); resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一个onFulfilled的返回值 } catch(e) { reject(e); // 捕获前面onFulfilled中抛出的异常 then(onFulfilled, onRejected); } }); }) } if (that.status === REJECTED) { // 失败态 return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try { let x = onRejected(that.reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); } if (that.status === PENDING) { // 等待态 // 当异步调用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中 return newPromise = new Promise((resolve, reject) => { that.onFulfilledCallbacks.push((value) => { try { let x = onFulfilled(value); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); that.onRejectedCallbacks.push((reason) => { try { let x = onRejected(reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); } }; /** * Promise.all Promise进行并行处理 * 参数: promise对象组成的数组作为参数 * 返回值: 返回一个Promise实例 * 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。 */ Promise.all = function(promises) { return new Promise((resolve, reject) => { let done = gen(promises.length, resolve); promises.forEach((promise, index) => { promise.then((value) => { done(index, value) }, reject) }) }) } function gen(length, resolve) { let count = 0; let values = []; return function(i, value) { values[i] = value; if (++count === length) { console.log(values); resolve(values); } } } /** * Promise.race * 参数: 接收 promise对象组成的数组作为参数 * 返回值: 返回一个Promise实例 * 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快) */ Promise.race = function(promises) { return new Promise((resolve, reject) => { promises.forEach((promise, index) => { promise.then(resolve, reject); }); }); } // 用于promise方法链时 捕获前面onFulfilled/onRejected抛出的异常 Promise.prototype.catch = function(onRejected) { return this.then(null, onRejected); } Promise.resolve = function (value) { return new Promise(resolve => { resolve(value); }); } Promise.reject = function (reason) { return new Promise((resolve, reject) => { reject(reason); }); } /** * 基于Promise实现Deferred的 * Deferred和Promise的关系 * - Deferred 拥有 Promise * - Deferred 具备对 Promise的状态进行操作的特权方法(resolve reject) * *参考jQuery.Deferred *url: http://api.jquery.com/category/deferred-object/ */ Promise.deferred = function() { // 延迟对象 let defer = {}; defer.promise = new Promise((resolve, reject) => { defer.resolve = resolve; defer.reject = reject; }); return defer; } /** * Promise/A+规范测试 * npm i -g promises-aplus-tests * promises-aplus-tests Promise.js */ try { module.exports = Promise } catch (e) { }
68、手写实现防抖函数
// 思路:在规定时间内未触发第二次,则执行 function debounce (fn, delay) { // 利用闭包保存定时器 let timer = null return function () { let context = this let arg = arguments // 在规定时间内再次触发会先清除定时器后再重设定时器 clearTimeout(timer) timer = setTimeout(function () { fn.apply(context, arg) }, delay) } } function fn () { console.log('防抖') } addEventListener('scroll', debounce(fn, 1000))
69、手写实现节流函数
// 思路:在规定时间内只触发一次 function throttle (fn, delay) { // 利用闭包保存时间 let prev = Date.now() return function () { let context = this let arg = arguments let now = Date.now() if (now - prev >= delay) { fn.apply(context, arg) prev = Date.now() } } } function fn () { console.log('节流') } addEventListener('scroll', throttle(fn, 1000))
拷贝方法总结
var cloneOfA = JSON.parse(JSON.stringify(a));可以用于简单对象的深拷贝。这个方法的原理是将对象转换成json字符串以后再将json字符串转换成对象。
因此要注意那些转换成json以后无法恢复的类型,最好只用来处理属性值是原始类型的对象和数组,或者它们的嵌套。此外,这个方法也不能处理存在环的对象。 Object.assign(target, ...sources)是ES6提供的浅拷贝方法,与我们给出的浅拷贝方法作用类似,拷贝对象自身的、可枚举的属性。
Object.assign可以传入多个source对象,并且target不要求是空对象。需要注意的是它拷贝streing、number、boolean原始类型的时候,会先将它们装箱,再拷贝这个包装对象:
Object.assign({}, 'abcd') // Object {0: "a", 1: "b", 2: "c", 3: "d"} var copiedObject = jQuery.extend({}, originalObject) 是jQuery提供的方法。默认是浅拷贝,它拷贝自身和原型链上的所有可枚举属性。可以通过设置第一个参数为true来进行深拷贝: var copiedObject = jQuery.extend(true, {}, originalObject) extend、assign这些单词的名字的意思是“扩展”、“赋值”,拷贝对象只是它们的用途之一,它们的target参数不一定是要{}。 Underscore的 _.clone(source) 浅拷贝,返回拷贝出的新对象。它拷贝自身和原型链上的所有可枚举属性。 lodash 的 _.clone(value) 和 _.cloneDeep()能够很好地处理很多内置对象:
arrays, array buffers, booleans, date objects, maps, numbers, Object objects, regexes, sets, strings, symbols, and typed arrays,
并且能处理存在环的对象,更接近完美。
70、手写实现浅拷贝
// 木易杨 function cloneShallow(source) { var target = {}; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } return target; } // 测试用例 var a = { name: "muyiy", book: { title: "You Don't Know JS", price: "45" }, a1: undefined, a2: null, a3: 123 } var b = cloneShallow(a); a.name = "高级前端进阶"; a.book.price = "55"; console.log(b); // { // name: 'muyiy', // book: { title: 'You Don\'t Know JS', price: '55' }, // a1: undefined, // a2: null, // a3: 123 // }
71、手写实现深拷贝
function deepClone(obj) { let objClone = Array.isArray(obj) ? [] : {}; if (obj && typeof obj === "object") { // for...in 会把继承的属性一起遍历 for (let key in obj) { // 判断是不是自有属性,而不是继承属性 if (obj.hasOwnProperty(key)) { //判断ojb子元素是否为对象或数组,如果是,递归复制 if (obj[key] && typeof obj[key] === "object") { objClone[key] = this.deepClone(obj[key]); } else { //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; }
//深拷贝最优版 function clone(target, map = new Map()) { if (typeof target === 'object') { let cloneTarget = Array.isArray(target) ? [] : {}; if (map.get(target)) { return map.get(target); } map.set(target, cloneTarget); for (const key in target) { cloneTarget[key] = clone(target[key], map); } return cloneTarget; } else { return target; } };
72、手写实现简单的Event模块的emit和on方法
function Events(){ this.on=function(eventName,callBack){ if(!this.handles){ this.handles={}; } if(!this.handles[eventName]){ this.handles[eventName]=[]; } this.handles[eventName].push(callBack); } this.emit=function(eventName,obj){ if(this.handles[eventName]){ for(var i=0;o<this.handles[eventName].length;i++){ this.handles[eventName][i](obj); } } } return this; } var events=new Events(); events.on('say',function(name){ console.log('Hello',nama) }); events.emit('say','Jony yu'); //结果就是通过emit调用之后,输出了Jony yu
手写实现一个按顺序加载的 promise
/* 使用 async await */ async function queue(tasks) { const res = [] for (let promise of tasks) { res.push(await promise(res)); } return await res } queue([a, b, c]) .then(data => { console.log(data) })
73、手写实现promise.then()方法
74、手写实现promise.all()方法
Promise.all 功能 Promise.all(iterable) 返回一个新的 Promise 实例。此实例在 iterable 参数内所有的 promise 都 fulfilled 或者参数中不包含 promise 时,
状态变成 fulfilled;如果参数中 promise 有一个失败rejected,此实例回调失败,失败原因的是第一个失败 promise 的返回结果。 let p = Promise.all([p1, p2, p3]); 复制代码p的状态由 p1,p2,p3决定,分成以下;两种情况: (1)只有p1、p2、p3的状态都变成 fulfilled,p的状态才会变成 fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 (2)只要p1、p2、p3之中有一个被 rejected,p的状态就变成 rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。 Promise.all 的特点 Promise.all 的返回值是一个 promise 实例 如果传入的参数为空的可迭代对象,Promise.all 会 同步 返回一个已完成状态的 promise 如果传入的参数中不包含任何 promise,Promise.all 会 异步 返回一个已完成状态的 promise 其它情况下,Promise.all 返回一个 处理中(pending) 状态的 promise. Promise.all 返回的 promise 的状态 如果传入的参数中的 promise 都变成完成状态,Promise.all 返回的 promise 异步地变为完成。 如果传入的参数中,有一个 promise 失败,Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
75、手写实现promise.on()方法
76、手写实现promise.finally()方法
Promise.prototype.finally = function (callback) { return this.then((value) => { return Promise.resolve(callback()).then(() => { return value; }); }, (err) => { return Promise.resolve(callback()).then(() => { throw err; }); }); }
77、手写实现promise.race()方法
在代码实现前,我们需要先了解 Promise.race 的特点: Promise.race返回的仍然是一个Promise. 它的状态与第一个完成的Promise的状态相同。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个Promise是哪一种状态。 如果传入的参数是不可迭代的,那么将会抛出错误。 如果传的参数数组是空,那么返回的 promise 将永远等待。 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。 Promise.race = function (promises) { //promises 必须是一个可遍历的数据结构,否则抛错 return new Promise((resolve, reject) => { if (typeof promises[Symbol.iterator] !== 'function') { //真实不是这个错误 Promise.reject('args is not iteratable!'); } if (promises.length === 0) { return; } else { for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]).then((data) => { resolve(data); return; }, (err) => { reject(err); return; }); } } }); }
78、手写实现EventBus()方法
// 组件通信,一个触发与监听的过程 class EventEmitter { constructor () { // 存储事件 this.events = this.events || new Map() } // 监听事件 addListener (type, fn) { if (!this.events.get(type)) { this.events.set(type, fn) } } // 触发事件 emit (type) { let handle = this.events.get(type) handle.apply(this, [...arguments].slice(1)) } } // 测试 let emitter = new EventEmitter() // 监听事件 emitter.addListener('ages', age => { console.log(age) }) // 触发事件 emitter.emit('ages', 18) // 18
79、手写实现双向数据绑定
let obj = {} let input = document.getElementById('input') let span = document.getElementById('span') // 数据劫持 Object.defineProperty(obj, 'text', { configurable: true, enumerable: true, get() { console.log('获取数据了') }, set(newVal) { console.log('数据更新了') input.value = newVal span.innerHTML = newVal } }) // 输入监听 input.addEventListener('keyup', function(e) { obj.text = e.target.value })
手写实现柯里化函数
//定义:函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术 function currying(fn,len){ //捕获函数需要传入 len 个函数后才可执行 //第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的长度 var len = len || fn.length; return function(){ //判断新函数接收的参数长度是否大于等于 fn 剩余参数需要接收的长度 if( arguments.length >= len ){ //参数一致则执行函数 return fn.apply(this,arguments) }else{ //不满足参数个数则递归 currying 函数传入 fn.bind() => 传入部分参数的新函数,并且重新定义新函数所需的参数个数 return currying(fn.bind(this,...arguments),len - arguments.length) } } } //测试 var test = currying(function(a,b,c){ return a + b + c; }) test(1)()(2,3) // 6 test()(1,2)(3) // 6 text(1)(2)(3) // 6
手写实现分时函数-解决大规模dom节点创建
var timeChunk=function (ary,fn,count) { var ovj,t; var len=ary.length; var start=function () { for (var i = 0; i) { var obj=ary.shift(); fn(obj); } }; return function () { t=setInterval(function () { //如果全部节点都已被创建好 if (ary.length===0){ return clearInterval(t); } start(); },200); } } //测试 var ary=[]; for (let i = 1; i <=1000 ; i++) { ary.push(i); } var renderlist=timeChunk(ary,function (n) { var div=document.createElement('div'); div.innerHTML=n; document.body.appendChild(div); },8); renderlist();
80、手写实现一个简单路由
// hash路由 class Route{ constructor(){ // 路由存储对象 this.routes = {} // 当前hash this.currentHash = '' // 绑定this,避免监听时this指向改变 this.freshRoute = this.freshRoute.bind(this) // 监听 window.addEventListener('load', this.freshRoute, false) window.addEventListener('hashchange', this.freshRoute, false) } // 存储 storeRoute (path, cb) { this.routes[path] = cb || function () {} } // 更新 freshRoute () { this.currentHash = location.hash.slice(1) || '/' this.routes[this.currentHash]() } }
81、手写实现封装Ajax
/* 封装ajax函数 * @param {string}opt.type http连接的方式,包括POST和GET两种方式 * @param {string}opt.url 发送请求的url * @param {boolean}opt.async 是否为异步请求,true为异步的,false为同步的 * @param {object}opt.data 发送的参数,格式为对象类型 * @param {function}opt.success ajax发送并接收成功调用的回调函数 */ function myAjax(opt){ opt = opt || {}; opt.method = opt.method.toUpperCase() || 'POST'; opt.url = opt.url || ''; opt.async = opt.async || true; opt.data = opt.data || null; opt.success = opt.success || function () {} let xmlHttp = null; if (XMLHttpRequest) { xmlHttp = new XMLHttpRequest(); }else{ xmlHttp =new ActiveXObject('Microsoft.XMLHTTP') } let params; for (var key in opt.data){ params.push(key + '=' + opt.data[key]); } let postData = params.join('&'); if (opt.method.toUpperCase() === 'POST') { xmlHttp.open(opt.method, opt.url, opt.async); xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8'); xmlHttp.send(postData); }else if (opt.method.toUpperCase() === 'GET') { xmlHttp.open(opt.method, opt.url + '?' + postData, opt.async); xmlHttp.send(null); } xmlHttp.onreadystatechange= function () { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { opt.success(xmlHttp.responseText);//如果是json数据可以在这使用opt.success(JSON.parse( xmlHttp.responseText)) } }; }
Vue纯前端实现Cookie登录记住账户功能
用户禁止cookie后,如何继续使用session
如果用户禁止cookie,服务器仍会将sessionId以cookie的方式发送给浏览器,但是,浏览器不再保存这个cookie(即sessionId)了,这时就需使用URL重写了 //1.什么是URL重写 浏览器在访问服务器上的某个地址时,不能够直接写这个组件的地址,而应该使用服务器生成的这个地址。 someServlet error "> encodeURL方法会在"some"后面添加sessionId。 //2.如何进行url重写。 //encodeURL方法用在链接地址、表单提交地址。 response.encodeURL(String url); //encodeRedirectURL方法用于重定向地址。 response.encodeRedirectURL(String url);
手写实现cookie
cookie是网站设计者放置在客户端的小文本文件,一般后台语言使用的比较多,可以实现用户个性化的一些需求。 javascript使用 document.cookie 来操作cookie 同一个域名下的页面,共有一个cookie 不同的浏览器分别管理自己的cookie,互不影响 // 设置cookie function setCookie(c_name, value, expiredays) { var exdate = new Date(); exdate.setDate(exdate.getDate() + expiredays); document.cookie = c_name + "=" + escape(value) + "; expires=" + exdate.toGMTString() + "; path=/"; } // 读取cookie function getCookie(c_name) { if (document.cookie.length > 0) { c_start = document.cookie.indexOf(c_name + "=") if (c_start != -1){ c_start = c_start + c_name.length + 1 c_end = document.cookie.indexOf(";", c_start) if (c_end == -1) c_end = document.cookie.length return unescape(document.cookie.substring(c_start, c_end)) } } return "" } // 检查cookie function checkCookie(c_name) { username = getCookie(c_name); console.log(username); if (username != null && username != "") { return true; } else { return false; } } // 清除cookie function clearCookie(name) { setCookie(name, "", -1); }