(答案持续更新...)
1.简述同步和异步的区别
js是一门单线程语言,所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。如果一个任务耗时过长,那么后面的任务就必须一直等待下去,会拖延整个程序,常见浏览器无反应,可能就是一段代码死循环,造成程序卡住在这个位置,无法继续
为了解决这个问题,js的执行模式分为两种:同步和异步。
"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;
"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
具体来说,异步运行机制如下:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步
2.怎么添加、移除、复制、创建、和查找节点
(1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
var para=document.createElement("p"); //创建新的 <p> 元素
var node=document.createTextNode("这是新段落。"); //创建了一个文本节点
para.appendChild(node); //向 <p> 元素追加这个文本节点
var element=document.getElementById("div1");//向一个已有的元素追加这个新元素
element.appendChild(para); //向这个已有的元素追加新元素
(2)添加、移除、替换、之前插入、之后插入、复制
appendChild()、removeChild()、replaceChild()、insertBefore()、insertAfter()、cloneNode()
(3)查找
document.getElementsByTagName("") //通过标签名称
document.getElementsByName("") //通过元素的Name属性的值
document.getElementById("") //通过元素Id,唯一性
document.getElementsByClassName(""); //通过类查找
document.querySelector("")
3.实现一个函数clone 可以对Javascript中的五种主要数据类型(Number、string、Object、Array、Boolean)进行复制
function clone(obj) {var o;switch (typeof obj) {case 'undefined':break;case 'string':o = obj + '';break;case 'number':o = obj - 0;break;case 'boolean':o = obj;break;case 'object': //object分为两种,一种为Object,一种为Arrayif (obj === null) {o = null} else {if (Object.prototype.toString.call(obj).slice(8, -1) === 'Array') {o = [];for (var i = 0; i < obj.length; i++) {o.push(clone(obj[i]))}} else {o = {};for (var k in obj) {o[k] = clone(obj[k])}}}break;default:o = obj;break;}return o;}
4.如何消除一个数组里面重复的元素
// 方法一:Array.prototype.clearRepeat = function () {var arr = []; //定义一个临时数组for (var i = 0; i < this.length; i++) {//通过遍历判断当前数组下标为i的元素是否保存到临时数组中//如果保存,则跳过,否则保存到临时数组if (arr.indexOf(this[i]) == -1) {arr.push(this[i]);}}return arr;};var test = [1, 6, 8, 8, 9, 9, 9, "a", "a"];test.clearRepeat(); //结果为[1, 6, 8, 9, "a"]// 方法二:Array.prototype.clearRepeat = function () {var arr = [this[0]]; //直接定义结果数组for (var i = 1; i < this.length; i++) { //从第二项开始遍历当前数组//对元素进行判断://如果当前数组元素在此数组中第一次出现的位置不是i//则第i项是重复的,否则直接存入结果数组if (this.indexOf(this[i]) == i) {arr.push(this[i]);}}return arr;};var test = [1, 6, 8, 8, 9, 9, 9, "a", "a"];test.clearRepeat(); //结果为[1, 6, 8, 9, "a"]
// 上面两种方法不推荐使用,因为indexOf()这个函数在执行的时候每次都会遍历一次数组,
//对性能影响比较大。比较适合小数据量的数组。//第三种使用的是hash表,把已经出现过的元素通过下标形式写入一个Object中,
//下标的引用要比数组的indexOf()方法搜索节省时间Array.prototype.clearRepeat = function() {var h = {}; //定义一个hash表var arr = []; //定义一个临时数组for (var i = 0; i < this.length; i++) {if (!h[this[i]]) {h[this[i]] = true;arr.push(this[i]);}}return arr;};var test = [1, 6, 8, 8, 9, 9, 9, "1", "a"];console.log(test.clearRepeat()) //结果为[1, 6, 8, 9, "a"]
//此方法有缺陷,作为下标在转换后会变成字符串,
//那么对于1和“1”这样不同类型的值会对应到同一个下标而被去重。例如obj[1]和obj['1'],是相同的Array.prototype.clearRepeat = function() {var h = {}; //定义一个hash表var arr = []; //定义一个临时数组for (var i = 0; i < this.length; i++) {var type = typeof this[i];if (!h[this[i] + type]) {h[this[i] + type] = true;arr.push(this[i]);}}return arr;};//不用hash表的解决办法吗,使用原始sort()排序后相邻两个数值进行比较Array.prototype.clearRepeat = function() {this.sort(); //数组排序var arr = [this[0]]; //定义结果数组for (var i = 1; i < this.length; i++) { //从第二项开始遍历当前数组//判断两个相邻元素是否相等,如果相等说明数据重复,否则将元素写入结果数组if (this[i] !== arr[arr.length - 1]) {arr.push(this[i])}}return arr}var test = [1, 6, 8, 8, 9, 9, 9, "1", "a"];
5.写一个返回闭包的函数
1.闭包函数是指有权访问另一个函数作用域中的变量的函数
2.创建闭包函数最常见的方式是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量
3.闭包的特点:1函数嵌套函数,2 函数内部可以引用外部的参数和变量 3 参数和变量不会被垃圾回收机制回收4.闭包的优点:1 希望一个变量长期驻扎在内存中 * 2 避免全局变量的污染 * 3 私有变量存在5.闭包的实现 1:函数嵌套函数 * 2 外层函数返回内层函数 * 3 外面有一全局变量接受外层函数6.缺点: 闭包使用不当,会造成内存污染,正常无法被垃圾回收机制清掉,IE低版本会造成内存泄漏function fun1() {var num = 1;return function () {num++;return num;}}var s = fun1()// console.log(s());// // 自执行函数 的闭包var fun3 = function () {var num = 1;return {b: 6,sum: function () {return num + this.b}}}();// console.log(fun3.sum());/** 把函数名当参数调用* 回调函数** */function a() {console.log('a');}function b() {console.log('b');}function c(fun) {fun()}// c(a), c(b)// 循环var num = 0;function a(fun) {console.log(123);fun(fun)}function a2(fun) {num++;console.log(num);if (num > 10) return;fun(a2)}a(a2)
6.使用递归完成1到100的累加
function add(num) {if (num == 1) {return num;} else {return num + add(num - 1)}}console.log(add(100));
7.Javascript有哪几种数据类型
1,基本类型:字符串类型(string),数字类型(number),布尔类型(boolean)
2,复杂类型:数组类型(array),对象类型(object),函数类型(function),正则类型(regexp)
3,空类型:undefine 和 null
8.如何判断数据类型
1、typeof
typeof 123, //"number"typeof 'dsfsf', //"string"typeof false, //"boolean"typeof [1,2,3], //"object"typeof {a:1,b:2,c:3}, //"object"typeof function(){console.log('aaa');}, //"function"typeof undefined, //"undefined"typeof null, //"object"typeof new Date(), //"object"typeof /^[a-zA-Z]{5,20}$/, //"object"typeof new Error() //"object"
Array,Object,null,Date,RegExp,Error这几个类型都被typeof判断为object,所以如果想要判断这几种类型,就不能使用typeof了。
Number,String,Boolean,Function,undefined,如果想判断这几种类型,那就可以使用typeof。
2、instanceof
123 instanceof Number, //false'dsfsf' instanceof String, //falsefalse instanceof Boolean, //false[1,2,3] instanceof Array, //true{a:1,b:2,c:3} instanceof Object, //truefunction(){console.log('aaa');} instanceof Function, //trueundefined instanceof Object, //falsenull instanceof Object, //falsenew Date() instanceof Date, //true/^[a-zA-Z]{5,20}$/ instanceof RegExp, //truenew Error() instanceof Error //true
instanceof运算符需要指定一个构造函数,或者说指定一个特定的类型,它用来判断这个构造函数的原型是否在给定对象的原型链上
Number,String,Boolean没有检测出他们的类型,但是如果使用下面的写法则可以检测出来
var num = new Number(123);
var str = new String('dsfsf');
var boolean = new Boolean(false);
还需要注意null和undefined都返回了false,这是因为它们的类型就是自己本身,并不是Object创建出来它们,所以返回了false。
3、constructor
constructor是prototype对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的。
var num = 123;
var str = 'abcdef';
var bool = true;
var arr = [1, 2, 3, 4];
var json = {name:'wenzi', age:25};
var func = function(){ console.log('this is function'); }
var und = undefined;
var nul = null;
var date = new Date();
var reg = /^[a-zA-Z]{5,20}$/;
var error= new Error();function Person(){}
var tom = new Person();// undefined和null没有constructor属性
console.log(tom.constructor==Person,num.constructor==Number,str.constructor==String,bool.constructor==Boolean,arr.constructor==Array,json.constructor==Object,func.constructor==Function,date.constructor==Date,reg.constructor==RegExp,error.constructor==Error
);
//所有结果均为true
除了undefined和null之外,其他类型都可以通过constructor属性来判断类型。
4、toString()
可以通过toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为thisArg。
var toString = Object.prototype.toString;toString.call(123); //"[object Number]"
toString.call('abcdef'); //"[object String]"
toString.call(true); //"[object Boolean]"
toString.call([1, 2, 3, 4]); //"[object Array]"
toString.call({name:'wenzi', age:25}); //"[object Object]"
toString.call(function(){ console.log('this is function'); }); //"[object Function]"
toString.call(undefined); //"[object Undefined]"
toString.call(null); //"[object Null]"
toString.call(new Date()); //"[object Date]"
toString.call(/^[a-zA-Z]{5,20}$/); //"[object RegExp]"
toString.call(new Error()); //"[object Error]"
总结以上的案例封装个方法
function gettype(obj) {var type = typeof obj;if (type !== 'object') {return type;}//如果不是object类型的数据,直接用typeof就能判断出来//如果是object类型数据,准确判断类型必须使用Object.prototype.toString.call(obj)的方式才能判断return Object.prototype.toString.call(obj).replace(/^[object (S+)]$/, '$1');
}
5、jQuery中判断的方法
jQuery.isArray(object)
jQuery.isFunction(value)
jQuery.isNumeric(value)
jQuery.isEmptyObject(obj)
jQuery.isPlainObject(value)
9.console.log(1+'2')和console.log(1-'2')的打印结果
‘12’和-1,第一个是因为是字符串拼接,第二种减法运算将2强制转化为数值类型进行的运算
10.Js的事件委托是什么,原理是什么
事件委托,通俗来说就是将元素的事件委托给它的父级或者更外级元素处理。
事件流包括三个阶段:
- 事件捕获:和冒泡类似,只不过事件的顺序相反。即是从上级节点传递到下级节点
- 目标阶段
- 事件冒泡:当下级节点触发某个事件的时候,该事件会逐级向上触发上级节点的同类事件
事件委托就是利用事件冒泡机制实现的
事件委托的优点:
- 只需要将同类元素的事件委托给父级或者更外级的元素,不需要给所有元素都绑定事件,减少内存空间占用,提升性能
- 使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以一样具有和其他元素一样的事件
11.如何改变函数内部的this指针的指向
常用变量取代: var _this = this
call()的用法
var obj = {text: '我的两个爱好:'
}function getHobby(a, b) {console.log(this.text + a + '和' + b)
}getHobby.call(obj, '足球', '羽毛球')
// 我的两个爱好:足球和羽毛球
apply()的用法
var obj = {text: '我的两个爱好:'
}function getHobby(a, b) {console.log(this.text + a + '和' + b)
}getHobby.apply(obj, ['足球', '羽毛球'])
// 我的两个爱好:足球和羽毛球
bind()的用法
var obj = {text: '我的两个爱好:'
}function getHobby(a, b) {console.log(this.text + a + '和' + b)
}getHobby.bind(obj, '足球', '羽毛球')()
// 我的两个爱好:足球和羽毛球
对比
getHobby.call(obj, '足球', '羽毛球')
getHobby.apply(obj, ['足球', '羽毛球'])
getHobby.bind(obj, '足球', '羽毛球')()
12.列举几种解决跨域问题的方式,且说明原理
1.什么是同源策略及其限制内容?
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。常见跨域场景如下图所示:
特别说明两点:
第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。
第二:在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。
这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。
2、跨域解决方案
1.jsonp
2.cors
3.postMessage
4.websocket
5. Node中间件代理(两次跨域)
6.nginx反向代理
7.window.name + iframe
8.location.hash + iframe
9.document.domain + iframe
webpack解决跨域
// 后端没设置跨域时,可使用webpack进行配置// devServer: {// // 代理// proxy: {// // 只要请求地址有'api'都会匹配上// "/api": {// target: "http://132.232.94.151:3005",// ws: true,// // 允许跨域// changeOrigin: true,// pathRewrite: {// "^/api": "" //通过pathRewrite重写地址,将前缀/api转为/// }// }// }// }
13.谈谈垃圾回收机制的方式及内存管理
14.写一个function ,清除字符串前后的空格
function trim(str) {if (str && typeof str === "string") {return str.replace(/(^s*)|(s*)$/g,""); //去除前后空白符}
}
15.js实现继承的方法有哪些
16.判断一个变量是否是数组,有哪些办法
17.let ,const ,var 有什么区别
一)var声明变量存在变量提升,let和const不存在变量提升
console.log(a); // undefined ===> a已声明还没赋值,默认得到undefined值
var a = 100;
console.log(b); // 报错:b is not defined ===> 找不到b这个变量
let b = 10;
console.log(c); // 报错:c is not defined ===> 找不到c这个变量
const c = 10;
二)let、const都是块级局部变量
{let a = 1
}
console.log(a) // undefined
const 的特性和 let 完全一样,不同的只是
1)声明时候必须赋值
const a
控制台报错
2)只能进行一次赋值,即声明后不能再修改
const a=1
a=2
控制台报错
3)如果声明的是复合类型数据,可以修改其属性
18.箭头函数与普通函数有什么区别
那么箭头函数有哪些特点?
- 更简洁的语法
- 没有this
- 不能使用new 构造函数
- 不绑定arguments,用rest参数...解决
- 使用call()和apply()调用
- 捕获其所在上下文的 this 值,作为自己的 this 值
- 箭头函数没有原型属性
- 不能简单返回对象字面量
- 箭头函数不能当做Generator函数,不能使用yield关键字
- 箭头函数不能换行
19.随机取1-10之间的整数
<script>
document.write(parseInt(10*Math.random())); //输出0~10之间的随机整数
document.write(Math.floor(Math.random()*10+1)); //输出1~10之间的随机整数
function RndNum(n){
var rnd="";
for(var i=0;i<n;i++)
rnd+=Math.floor(Math.random()*10);
return rnd;
}
document.write(RndNum(4)); //输出指定位数的随机数的随机整数引用部分:
1. 从1开始 至 任意值parseInt(Math.random()*上限+1);
2. 从任意值开始 至 任意值parseInt(Math.random()*(上限-下限+1)+下限); function fRandomBy(under, over){
switch(arguments.length){
case 1: return parseInt(Math.random()*under+1);
case 2: return parseInt(Math.random()*(over-under+1) + under);
default: return 0;
}
}
document.write(fRandomBy(1,100)); //输出指定范围内的随机数的随机整数