谈谈数组Array在项目中常见的场景
- 求和,求最大(小)值,排序,多个数组合并(concat),去重,分割,find,indexOf,filter,join, toString等等
- 数组的concat,join,slice,toString方法不会改变原数组
- 数组的splice,push,pop,unshift,shift,sort,reverse方法会改变原数组
类数组怎么转化为数组
什么是类数组,就是属性要为索引(数字)属性,同时又必须有一个length属性来控制对象边界的一个特殊对象,特点:
1.不能使用数组的方法,他们不能使用Array的方法
2.拥有length属性,可以使用下标来访问元素,这两点和数组相同。
var obj = {
'0': 'a',
'1': 'b',
'2': 'c',
'length': 3
}
转化为数组的方法有3种:
- 第一种,采用es6中的新方法Array.from()
var myArray = ["value1", "value2", "value3"];
var mySet = new Set(myArray);
console.log(Array.from(mySet)); //第一种方法Array.from()
console.log([...mySet]); //第二种方法Array.from()
-
第二种,创建新数组,通过forEach遍历把类数组的元素添加到新数组中
-
第三种,通过Array.prototype.slice.call(arrayLike, 0)或者[].slice.call(arrayLike, 0)将类数组对象传入即可
-
第四种,扩展运算符
...
[...arguments]
// 将参数转为数组
var fn = function() {
console.log([...arguments]);
}
fn(1, 2, 3)
// 展开Set值
[...set.values()]
common.js和ES6模块化的区别
详情可见:
js的4种模块化的使用方法和区别
import、require、export、module.exports 混合使用详解
不同规范产生的来由啊,随着技术的发展,js这门语言模块化的需要也越来迫切,随之而来就是各种规范,什么CMD、AMD各种规范也应运而生,现在在服务端形成CommonJS规范,前端呢ES6模块化的概念也深入人心。
毕竟一个是用于后端,一个是用于前端,所以还是有很大的不同,比较一下common.js和ES6模块化的区别:
-
前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持。
-
前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响。
-
前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
-
require 在 ES6(bable将import转化为require) 和 CommonJS 中都支持,像Vue中一般引入CSS样式的时候会用到require,是因为样式一般需要同步导入的原因所致
-
即使我们使用了 ES6 的模块系统,如果借助 Babel 的转换,ES6 的模块系统最终还是会转换成 CommonJS 的规范。
两个数据互换,比如a=6&b=5,怎么做能a=5&b=6
// 第一种方法
var a = 10;
var b = 20;
var tmp = a; // tmp = 10;
a = b; // a = 20;
b = tmp; // b = 10;
// 第二种方法
var a = 10;
var b = 20;
a = a + b;
b = a - b;
a = a - b;
// 第三种方法
var a = 10;
var b = 20;
var temp = {
a: b,
b: a
};
b = temp.b;
a = temp.a;
// console.log(a, b);
TS中枚举的定义
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等,通过enum关键字定义
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days[0] === 'Sun'); //true
console.log(Days['Sat'] === 6); //true
Set和Map
JavaScript的默认对象表示方式{}可以视为其他语言中的Map或Dictionary的数据结构,即一组键值对。
但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。为了解决这个问题,最新的ES6规范引入了新的数据类型Map和set.
- Set [类数组] 集合是由一组无序且唯一(即不能重复)的项组成的,可以想象成集合是一个既没有重复元素,也没有顺序概念的数组
var a = new Set([1, 2, 3, {"1": "2"}, ["3","4"]])
- Map [类对象] 它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适
var m = new Map([['name', 'zhangsan'],['sex', 'male']]);
console.log(m); //Map {"name" => "zhangsan", "sex" => "male"}
防抖与节流的原理及实现
节流 (throttle) 先执行,过一段时间再执行第二次
function throttlePro(delay, action) {
var tId;
return function () {
var context = this;
var arg = arguments;
if (tId) return;
tId = setTimeout(function () {
action.apply(context, arg);
clearTimeout(tId);
// setTimeout 返回一个整数,clearTimeout 之后,tId还是那个整数,setInterval同样如此
tId = null;
}, delay);
}
}
去抖 (debounce) 直至事件结束后,在处理,比如resize/scroll
function debounce(delay, action) {
var tId;
return function () {
var context = this;
var arg = arguments;
if (tId) clearTimeout(tId);
tId = setTimeout(function () {
action.apply(context, arg);
}, delay);
}
}
window.onscroll = debounce(1000, print);
实现原理:利用定时器setTimeout
展开(扩展)运算符 ...
的使用场景
- 替换Apply的函数调用
// 使用扩展运算符
var array1 = [1, 2, 3];
Math.min(...array1);
- 数组合并
// 数组合并
var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
var array3 = [...array1, ...array2, 7, 8]; //[1,2,3,4,5,6,7,8]
array1.push(...array2 )// 当然也可以使用concat等数组合并方法,但是扩展运算符提供了一种新的方式
- 转换类数组为数组
// es5
var fn = function() {
console.log(Array.prototype.slice.apply(arguments));
}
fn(1,2,3)
//扩展运算符
var fn = function() {
console.log([...arguments]);
}
fn(1,2,3)
怎么拍平一个二维数组(数组的扁平化)
核心原因, 用Array.isArray判断每一项
js数组拍平(数组扁平化)的六种方式
怎么判断一个目标类型
-
typeof
- 对于数组、函数、对象来说,其关系错综复杂,使用 typeof 都会统一返回 “object” 字符串,
- null也会返回’object’
- 对NaN返回是’number’
-
instanceof
- 和typeof一样,
[] instanceof Array
和[] instanceof Object
都是返回true
- 和typeof一样,
-
Object.prototype.toString.call()
Object.prototype.toString.call("jerry"); //[object String]
Object.prototype.toString.call(12); //[object Number]
Object.prototype.toString.call(true)); //[object Boolean]
function Person(){};
Object.prototype.toString.call(new Person); //[object Object]
// 判断是否为函数
function isFunction(it) {
return Object.prototype.toString.call(it) === '[object Function]';
}
// 判断是否为数组:
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]';
}
babel常用到的工具包
* babel: ES6转义的核心包
* babel-cli: 用于在终端使用babel,用命令行转码
* babel-core: 如果某些代码需要调用Babel的API进行转码,就要使用`babel-core`模块
* babel-loader: 执行转义的核心包
* babel-plugin-react-transform: 代替react-hot-loader的插件
* babel-preset-es2015: 现在被babel-preset-env取代了,被babel未来不会过时的(future-proof)”解决方案
* babel-preset-react: 转义react的jsx语法,
* babel-preset-stage-0: `stage-0`包含`stage-1`, `stage-2`以及`stage-3`的所有功能
Vue中一个页面能否有多个插槽,怎么区分
vue中的插槽(slot)
vue中的插槽,父组件向子组件传递填充的模板代码,子组件中提供给父组件使用的一个占位符,用<slot></slot>
标签表示,比如HTML、组件等,填充的内容会替换掉子组件的<slot></slot>
标签(替换占位符)。
如果需要多个占位符,则直接加name区分即可,即
// 子组件插槽
<div>
<span>我是第一个插槽</span>
<slot name="yanggb1"></slot>
<span>我是第二个插槽</span>
<slot name="yanggb2"></slot>
</div>
// 父组件插入模板
<template v-slot:yanggb1>
<span>yanggb1</span>
</template>
<template v-slot:yanggb2>
<span>yanggb2</span>
</template>
Vue中data为啥不直接返回一个对象,而要return出去
不使用return包裹的数据会在项目的全局可见
,会造成变量污染,使用return包裹后数据中变量只在当前组件中生效
,不会影响其他组件
函数作用域和函数执行顺序
作用域有全局变量
和局部变量
(也称为函数作用域
)两类, ES6新增块级作用域,通过新增命令let
和const
来体现
function func(args){
if (true) {
//let声明i
let i = 6;
//在if内打印i值
console.log('inside: ' + i);//6
}
//在if外,再次打印i值
console.log('outside: ' + i);//i is not defined
};
关于执行顺序,主要是由函数的定义
方式中函数声明式定义 function student(){}
所引起的变量置顶提升
聊聊new Function这个陌生面孔及函数作用域和函数执行顺序
js的深拷贝和浅拷贝
浅拷贝的时候如果数据是基本数据类型,那么就如同直接赋值那种,会拷贝其本身,如果除了基本数据类型之外还有一层对象,那么对于浅拷贝而言就只能拷贝其引用,对象的改变会反应到拷贝对象上;但是深拷贝就会拷贝多层,即使是嵌套了对象,也会都拷贝出来。
简单的拷贝方法(即对象中的值没有函数,数组,对象等复杂类型)
// 第一种 循环赋值
function simpleClone(initalObj) {
var obj = {};
for ( var i in initalObj) {
obj[i] = initalObj[i];
}
return obj;
}
// 第二种 Object.assign
var obj2 = { a: 10, b: 20, c: 30 };
var cloneObj2 = Object.assign({}, obj2);
cloneObj2.b = 100;
console.log(obj2);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(cloneObj2);
// { a: 10, b: 100, c: 30 }
// 第三种 JSON.parse(JSON.stringify(obj1)) 一般处理json数据时才会使用
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
(终于弄清楚JS的深拷贝和浅拷贝了)[https://blog.youkuaiyun.com/QTFYING/article/details/90407551]
聊聊Promise
诞生的原因:在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,最典型的就是ajax,最终被ES6纳入标准,生成Promise
判断下面函数的执行顺序
console.log(1)
setTimeout(()=>{console.log(2)},1000)
async function fn(){
console.log(3)
setTimeout(()=>{console.log(4)},20)
return Promise.reject()
}
async function run(){
console.log(5)
await fn()
console.log(6)
}
run()
//需要执行150ms左右
for(let i=0;i<90000000;i++){}
setTimeout(()=>{
console.log(7)
new Promise(resolve=>{
console.log(8)
resolve()
}).then(()=>{console.log(9)})
},0)
console.log(10)
闭包
定义:
-
百度百科:
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。 -
《JavaScript高级编程指南》:
闭包是指有权访问另外一个函数作用域中的变量的函数。 -
MDN:
闭包是由函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量
有什么作用呢:
- 模仿块级作用域(匿名函数)
- 储存变量,延长变量生命周期
- 封装私有变量
- 函数柯里化
// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
check(/\d+/g, 'test') //false
check(/[a-z]+/g, 'test') //true
// Currying后
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
关于闭包的this指向
var name = "听风是风";
var obj = {
name: "行星飞行",
sayName: function () {
var that = this;
return function () {
console.log(this.name); //this -> window -> 听风是风
console.log(that.name);
};
}
};
obj.sayName()(); // 行星飞行