大部分来源百度,有些部分加上自己理解
目录:
- javascript的typeof返回哪些数据类型
- 强制类型转换和隐式类型转换
- split() join() 的区别
- js数组常见方法
- get和post的区别
- call和apply
- 事件冒泡事件委托
- js闭包
- 阻止默认事件
- 操作DOM
- JS变量范围、作用域
- var,let,const区别
- js的变量提升,函数提升
- null、NaN和undefined的区别总结
- js中== 和===比较规则
- 获取页面上所有选中的input框,使用原生js
- js延迟加载的几种方式
- 判断一个字符串中出现次数最多的字符,统计这个次数
- 去掉数组中的重复元素
- a++ 与++a的区别
- js中判断两个对象是否的相等的几种方法
- setTimeout和setInterval的区别
1. javascript的typeof返回哪些数据类型
typeof 返回的数据类型有
boolean,string,number,object,function,undifined
其实相当于从里一个角度来回答js有哪些数据类型:
JS的数据类型: Number,Boolean,String,Undefined,Null,Symbol(es6新定义的)和 Object (注: Array是特殊的Object)
2.强制类型转换和隐式类型转换
- 强制类型转换代表的方法有:parseFloat,parseNumber,parse
- 隐式类型转换:==
隐式类型转换规则:
参考文章:https://www.cnblogs.com/ljk001/archive/2017/12/22/8086084.html
(1)对象的比较规则
-
对象与布尔值,字符串比较时,会先将对象转换为字符串,然后再进行比较
[1] == true; //fasle,[]转换为字符串'',然后转换为数字0,true转换为数字1,所以为false [1,2,3] == '1,2,3' // true [1,2,3]转化为'1,2,3',然后和'1,2,3', so结果为true;
-
对象和数字比较时,会先将对象转成字符串,然后再转换成数字,然后再进行比较
[1] == 1; // true `对象先转换为字符串再转换为数字,二者再比较 [1] => '1' => 1 所以结果为true
(2)字符串比较规则
-
字符串与数字相比较时,会先将字符串转换成数字,然后再进行比较
'1' == 1 // true
-
字符串与布尔值比较时,会将两者全部转换成数字然后再进行比较
'1' == true; // true
(3)布尔值比较规则
-
布尔值和数字比较时,会先将布尔值转换成数字,然后再进行比较
true == 1 // true
(4)特殊的比较
来看一个有趣的题:
[] == false; // true
![] == false;// true
这两个的结果都是true,第一个是,对象 => 字符串 => 数值0 false转换为数字0,这个是true应该没问题
第二个前边多了个!,则直接转换为布尔值再取反,转换为布尔值时,空字符串(’’),NaN,0,null,undefined这几个外返回的都是true, 所以! []这个[] => true 取反为false,所以[] == false为true。
另外:
undefined == null //true undefined和null 比较返回true,二者和其他值比较返回false,
Number(null) //0
隐式类型转换记住这么一个转换规则:
对象->字符串->数值
布尔->数值
3.split() join() 的区别
-
split():将字符串按分隔符组成一个数组
let str = '1,2,3,4,5'; let strArr = str.split(','); console.log(strArr); // ["1", "2", "3", "4", "5"]
-
join:将数组拼接成字符串
let arr = ['a','b','c']; console.log(arr.join('')); // abc
4.js数组常见方法
- push:向数组尾部添加元素
- pop:弹出元素,即数组尾部删除元素
- unshift:数组头部添加元素
- shift:数组头部删除元素
- concat:合并俩数组
- reverse:反转数组
- forEach:用来遍历
- slice:获取数组的切片,即获取部分内容
- splice:删除并添加数组
slice用法:
// slice(start,end);接受两个参数,start代表起始下标,end代表结束下标,左开右闭
let arr = ['a','b','c','d','e','f','g'];
// 传入一个参数时:代表从start索引位置一直到数组结束
console.log(arr.slice(2)); // ["c", "d", "e", "f", "g"]
// 两个参数:左开右闭,不包含end下标元素
console.log(arr.slice(2,5));// ["c", "d", "e"]
splice用法:
删除元素并添加元素,从index位置开始删除howmany个元素,并将arr1、arr2…数据从index位置依次插入。howmany为0时,则不删除元素。这个函数会将原数组改变。
let arr = ['a','b','c','d','e','f','g'];
arr.splice(1,2,'B','C');
console.log(arr); // ["a", "B", "C", "d", "e", "f", "g"]
5.get和post的区别
老生常谈的问题了:
-
get:适合参数较少情况下传输,4KB限制,拼接在url参数后面,不安全
-
post:参数放在请求体当中,适合多种参数类型传输,既可以以form形式提交参数,还可以发送json数据
-
更深一点的区别,说完这个面试官就会觉得这个小伙子不简单
GET和POST还有一个重大区别,简单的说:GET产生一个TCP数据包;POST产生两个TCP数据包
- 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
- 而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)
6.call和apply
这俩方法的定义就是:调用一个对象的一个方法,用另一个对象替换当前对象。说直白一点,就是俩对象A,B,A对象能调用B对象中的方法。
所以call和apply 都是用来修改函数中this的指向问题
来看个例子:(参考文章:https://blog.csdn.net/qq_21397815/article/details/90482341)
var name = 'Evan';
var age = 20;
var person = {
name: 'Hillary',
age: 19,
sayIntroduce: function () {
return "Hello, My name is " + this.name + " and I'm " + this.age + ' years old.'
},
sayHobby: function (val1, val2) {
return "I'm " + this.name + ", I like " + val1 + " and " + val2 + ".";
}
}
var person1 = {
name: 'Coy'
}
// 正常的方法调用
console.log(person.sayIntroduce());
当我们通过 call 和 apply 来this的指向时,不传任何参数,则默认为将this指向修改为 windows
// 当没有参数时,默认将this指向 window
console.log(person.sayIntroduce.call()); // Hello, My name is Evan and I'm 20 years old.
console.log(person.sayIntroduce.apply()); // Hello, My name is Evan and I'm 20 years old.
当需要传递参数时,call可以直接写多个参数,apply需要用数组方式传递:
console.log(person.sayHobby.call(person1, 'swimming', 'hiking')); // I'm Coy, I like swimming and hiking.
console.log(person.sayHobby.apply(person1, ['swimming', 'hiking'])); // I'm Coy, I like swimming and hiking.
所以:
- 当一个对象需要调用另外一个对象里面的方法的时候就可以用到call和apply
- 想要改变this指向时用这俩方法
7.事件冒泡事件委托
-
事件冒泡:JS中当出发某些具有冒泡性质的事件是,首先在触发元素寻找是否有相应的注册事件,如果没有再继续向上级父元素寻找是否有相应的注册事件作出相应,这就是事件冒泡。注意这里传递的仅仅是事件 并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件 也不会有什么表现 但事件确实传递了。)
详解请参考:https://www.cnblogs.com/moqing/p/5590216.html
-
事件委托:利用事件冒泡的特性,将本应该注册在子元素上的处理事件注册在父元素上,这样点击子元素时发现其本身没有相应事件就到父元素上寻找作出相应。这样做的优势有:1.减少DOM操作,提高性能。2.随时可以添加子元素,添加的子元素会自动有相应的处理事件
详情请参考:https://zhuanlan.zhihu.com/p/26536815
-
阻止事件冒泡:ie:阻止冒泡ev.cancelBubble = true;非IE ev.stopPropagation();
8.js闭包
闭包的本质就是在一个函数内部创建另一个函数,有限权访问另一个函数内部的变量。所以闭包就是能够读取其他函数内部变量的函数
参考:https://www.cnblogs.com/itjeff/p/10106855.html
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
闭包有3个特性:
-
函数嵌套函数
-
函数内部可以引用函数外部的参数和变量
-
参数和变量不会被垃圾回收机制回收
-
不需要调用,可以自我执行
闭包的好处与坏处
好处
- 私有变量,保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
- 变量可以长期保持在内存中
- 避免全局变量的污染
坏处
-
其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
-
其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
思考:
如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。
代码片段一。
var name = “The Window”;
var object = {
name : “My Object”,getNameFunc : function(){
return function(){
return this.name;
};}
};
alert(object.getNameFunc()());
代码片段二。
var name = “The Window”;
var object = {
name : “My Object”,getNameFunc : function(){
var that = this;
return function(){
return that.name;
};}
};
alert(object.getNameFunc()());
提示:this的指向是由它所在函数调用的上下文决定的,而不是由它所在函数定义的上下文决定的
9.阻止默认事件
两种方法:
- return false
- event.prentDefault()
例子:
当点击这个链接时,因为 onclick 返回的是false,所以这个链接的默认行为没有被触发
<a href="http://www.baidu.com" onclick="return false;">Click me</a>
另外,触发函数并且阻止默认事件达到使用a标签无法进行跳转
<a href="https://www.baidu.com" onclick="goBaidu(event)">Click me</a>
function goBaidu(event) {
event.preventDefault();
}
10.操作DOM
-
获取节点:
-
- document.getElementById(idName) //通过id号来获取元素,返回一个元素对象
- document.getElementsByName(name) //通过name属性获取id号,返回元素对象数组
- document.getElementsByClassName(className) //通过class来获取元素,返回元素对象数组(ie8以上才有)
- document.getElementsByTagName(tagName) //通过标签名获取元素,返回元素对象数组
-
获取/设置元素的属性值:
-
- element.getAttribute(attributeName) //括号传入属性名,返回对应属性的属性值
- element.setAttribute(attributeName,attributeValue) //传入属性名及设置的值
-
创建节点Node:
-
- document.createElement(“h3”) //创建一个html元素,这里以创建h3元素为例
- document.createTextNode(String); //创建一个文本节点;
- document.createAttribute(“class”); //创建一个属性节点,这里以创建class属性为例
-
增添节点:
-
- element.appendChild(Node); //往element内部最后面添加一个节点,参数是节点类型
- elelment.insertBefore(newNode,existingNode); //在element内部的中在existingNode前面插入newNode
-
删除节点:
-
- element.removeChild(Node) //删除当前节点下指定的子节点,删除成功返回该被删除的节点,否则返回null
11.JS变量范围、作用域
js分为两种变量范围
- 全局变量:声明在全局中的变量
- 局部变量:声明在函数体中的变量
下面看一个例子
// 声明全局变量,其实会被挂载到window对象上
var i = 2;
function test(){
// 这里声明的局部变量i与全局变量冲突,所以这个函数内部i的值会覆盖全局变量
var i = 1;
}
test();
alert(i);// 这里访问的是全局变量i,等同于alert(windows.i);
我们把函数内部的i去掉
var i = 2;
function test(){
// 这里其实是修改了全局变量
i = 1;
}
test();
alert(i);//值为1
上面函数不使用var来定义i,所以这里其实更改的是全局变量i的值,所以i=1
如果我们方法体里面变量名是一样,但是我又要用到全局的变量怎么办呢,使用window对象访问即可,例如:alert(window.i)
另外:
JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域。
块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。
例子:
{
var a = 1;
console.log(a); // 1
}
console.log(a); // 1
// 可见,通过var定义的变量可以跨块作用域访问到。
for(var i = 0; i < 4; i++) {
var d = 5;
};
console.log(i); // 4 (循环结束i已经是4,所以此处i为4)
console.log(d); // 5
if语句和for语句中用var定义的变量可以在外面访问到,可见,if语句和for语句属于块作用域,不属于函数作用域。
在补充一个作用域链概念:
如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
12.var,let,const区别
- var:块外可以访问,函数外不能访问,初始化时可以不赋值
- let:块外不能访问,函数外也不能访问,初始化时可以不赋值
- const:表示常量,块外不能访问,函数外不能访问,并且初始化时必须赋值
13.js的变量提升,函数提升
参考文章:https://www.cnblogs.com/lvonve/p/9871226.html
**变量提升:**变量在声明提升的时候,是全部提升到作用域的最前面,一个接着一个的。但是在变量赋值的时候就不是一个接着一个赋值了,而是赋值的位置在变量原本定义的位置。原本js定义变量的地方,在js运行到这里的时候,才会进行赋值操作,而没有运行到的变量,不会进行赋值操作。
所以变量的提升,提升的其实是变量的声明,而不是变量的赋值。
**函数提升:**函数的提升也会提升到最前面,会紧接着声明变量的下面,而且会将整个函数提升到前面,相当于剪切了过去
例子
下面的代码输出什么?
function foo() {
console.log(a);
var a = 1;
console.log(a);
function a() {}
console.log(a);
}
foo();
上面的代码在js眼中是这样解析的:
function foo() {
var a;
function a() {}
console.log(a); // a()
a = 1;
console.log(a); // 1
console.log(a); // 1
}
foo();
所以从上面的栗子可以看到,变量的提升是在函数提升之前的,但是变量赋值的部分是在js原型到变量定义的位置才给变量赋值的,而函数提升是相当于直接剪切到最前面的。
再看一个例子:
var a = 1;
function foo() {
a = 10;
console.log(a);
return;
function a() {};
}
foo();
console.log(a);
var a = 1; // 定义一个全局变量 a
function foo() {
// 首先提升函数声明function a () {}到函数作用域顶端, 然后function a () {}等同于 var a = function() {};最终形式如下
var a = function () {}; // 定义局部变量 a 并赋值。
a = 10; // 修改局部变量 a 的值,并不会影响全局变量 a
console.log(a); // 打印局部变量 a 的值:10
return;
}
foo();
console.log(a); // 打印全局变量 a 的值:1
另外:仍要注意,只有函数声明式才会被提升,字面量式不会被提升
例如:
// 这种不会被提升,而是和之前的变量提升规则一样
var fn = function(){ ... };
console.log(f1); // function f1() {}
console.log(f2); // undefined
function f1() {}
var f2 = function() {}
14.null、NaN和undefined的区别总结
先来看一段代码:
var a1;
var a2 = true;
var a3 = 1;
var a4 = "Hello";
var a5 = new Object();
var a6 = null;
var a7 = NaN;
var a8 = undefined;
alert(typeof a); //显示"undefined"
alert(typeof a1); //显示"undefined"
alert(typeof a2); //显示"boolean"
alert(typeof a3); //显示"number"
alert(typeof a4); //显示"string"
alert(typeof a5); //显示"object"
alert(typeof a6); //显示"object"
alert(typeof a7); //显示"number"
alert(typeof a8); //显示"undefined"
从上面的代码中可以看出未定义的值和定义未赋值的为undefined,null是一种特殊的object,NaN是一种特殊的number
比较运算
var a1; //a1的值为undefined
var a2 = null;
var a3 = NaN;
alert(a1 == a2); //显示"true"
alert(a1 != a2); //显示"false"
alert(a1 == a3); //显示"false"
alert(a1 != a3); //显示"true"
alert(a2 == a3); //显示"false"
alert(a2 != a3); //显示"true"
alert(a3 == a3); //显示"false"
alert(a3 != a3); //显示"true"
从上面的代码可以得出结论:(1)undefined与null是相等(全等除外);(2)NaN与任何值都不相等,与自己也不相等。
-
对象模型中,所有的对象都是Object或其子类的实例,但null对象例外:
document.writeln(null instanceof Object); //return false -
null等值(==)于undefined,但不“全等值 (===)于undefined:
document.writeln(null == undefined); //return true
document.writeln(null === undefined); //return false -
运算时null与undefined都可以被类型转换为false,但不等值于false:
document.writeln(!null, !undefined); //return true,true
document.writeln(null==false); //return false
document.writeln(undefined==false); //return false
15.js中== 和===比较规则
来源:https://www.cnblogs.com/nelson-hu/p/7922731.html
简单来说: == 代表相同, ===代表严格相同, 为啥这么说呢,
这么理解: 当进行双等号比较时候: 先检查两个操作数数据类型,如果相同, 则进行===比较, 如果不同, 则愿意为你进行一次类型转换, 转换成相同类型后再进行比较, 而===比较时, 如果类型不同,直接就是false.
操作数1 == 操作数2, 操作数1 === 操作数2
比较过程:
双等号==:
(1)如果两个值类型相同,再进行三个等号(===)的比较
(2)如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:
1)如果一个是null,一个是undefined,那么相等
2)如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较
三等号===:
(1)如果类型不同,就一定不相等
(2)如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相等。(判断一个值是否是NaN,只能使用isNaN( ) 来判断)
(3)如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。
(4)如果两个值都是true,或是false,那么相等
(5)如果两个值都引用同一个对象或是函数,那么相等,否则不相等
(6)如果两个值都是null,或是undefined,那么相等
16.获取页面上所有选中的input框,使用原生js
使用document.getElementsByTagName(‘input’)只能选中所有的input元素,还需要判断是不是checkbox,是checkbox的话是否选中。实现代码如下
<input type="checkbox" value="1">
<input type="checkbox" value="2">
<input type="checkbox" value="3" checked>
<input type="checkbox" value="4">
<input type="checkbox" value="5" checked>
<input type="txtxarea" value="6">
<input type="checkbox" value="7">
<input type="submit" value="8">
var arr = document.getElementsByTagName("input");
for(let i=0;i<arr.length;i++){
if(arr[i].getAttribute('type')=='checkbox'&&arr[i].hasAttribute('checked')){
arr [i].style.float = "right";
}
}
17.js延迟加载的几种方式
参考:https://blog.csdn.net/meijory/article/details/76389762
这是一个面试经常问到的问题:js的延迟加载方法 (js的延迟加载有助于提高页面的加载速度)主要考察对程序的性能方面是否有研究
-
方式一:使用defer属性
HTML 4.01 为
<script>
标签定义了defer
属性。
用途:表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕之后再执行。在
<script>
元素中设置defer
属性,等于告诉浏览器立即下载,但延迟执行。<!DOCTYPE html> <html> <head> <script src="test1.js" defer="defer"></script> <script src="test2.js" defer="defer"></script> </head> <body> <!-- 这里放内容 --> </body> </html> 12345678910
说明:虽然
<script>
元素放在了<head>
元素中,但包含的脚本将延迟浏览器遇到</html>
标签后再执行。HTML5
规范要求脚本按照它们出现的先后顺序执行。在现实当中,延迟脚本并不一定会按照顺序执行。defer
属性只适用于外部脚本文件。支持 HTML5 的实现会忽略嵌入脚本设置的defer
属性。 -
方式二:使用Html5属性async
HTML5 为
<script>
标签定义了async
属性。与defer
属性类似,都用于改变处理脚本的行为。同样,只适用于外部脚本文件。
目的:不让页面等待脚本下载和执行,从而异步加载页面其他内容。异步脚本一定会在页面 load 事件前执行。
不能保证脚本会按顺序执行。<!DOCTYPE html> <html> <head> <script src="test1.js" async></script> <script src="test2.js" async></script> </head> <body> <!-- 这里放内容 --> </body> </html> 12345678910
async和defer一样,都不会阻塞其他资源下载,所以不会影响页面的加载。
缺点:不能控制加载的顺序 -
方式三:动态创建DOM
这些代码应被放置在</body>标签前(接近HTML文件底部)
<script type="text/javascript"> function downloadJSAtOnload() { varelement = document.createElement("script"); element.src = "defer.js"; document.body.appendChild(element); } if (window.addEventListener) window.addEventListener("load",downloadJSAtOnload, false); else if (window.attachEvent) window.attachEvent("onload",downloadJSAtOnload); else window.onload =downloadJSAtOnload; </script>
18.判断一个字符串中出现次数最多的字符,统计这个次数
let str = 'asdfssaaasasasasaa';
let strArr = str.split('');
let obj = {};
strArr.forEach(((value, index, array) => {
// 这里其实就是巧妙的用了obj的k,v
if(obj[value]){
obj[value]++;
}else {
obj[value] = 1;
}
}));
console.log(obj);
19.去掉数组中的重复元素
-
方法一:有点作弊,直接利用set
var arr = [1,2,3,1,43,12,12,1]; let set = new Set(arr); console.log(set);
-
方法二:利用俩数组
let arr = [1,2,3,1,43,12,12,1]; let arr2 = []; for(let i=0;i<arr.length;i++){ if(!arr2.includes(arr[i])){ arr2.push(arr[i]); } } console.log(arr2);
20. a++ 与++a的区别
- a++ :存储新值,用旧值(上一个值)来计算,也就是输出旧值;
- ++a :存储新值,用新值(当前的值)来计算,也就是输出新值
计算:
var a = 2;
console.log(a++ + ++a + a++ + ++a) //输出16
//新值 3 4 5 6
//计算 2 + 4 + 4 + 6 = 16
21.js中判断两个对象是否的相等的几种方法
-
方法一:利用JSON.parse判断
优点:用法简单,对于顺序相同的两个对象可以快速进行比较得到结果
缺点:这种方法有限制就是当两个对比的对象中key的顺序不是完全相同时会比较出错
JSON.stringify(obj)==JSON.stringify(obj);//true
-
方法二:利用Object.is
这个方法判断是两个对象引用地址是否一致
let obj1= { a: 1 } let obj2 = { a: 1 } console.log(Object.is(obj1, obj2)) // false let obj3 = obj1 console.log(Object.is(obj1, obj3)) // trueconsole.log(Object.is(obj2, obj3)) // false
-
方法三:判断内容是否相等,这个就比较麻烦了,需要的考虑的问题比较多
let obj1 = { a: 1, b: { c: 2 } } let obj2 = { b: { c: 3 }, a: 1 } function isObjectValueEqual(a, b) { // 判断两个对象是否指向同一内存,指向同一内存返回true if (a === b) return true // 获取两个对象键值数组 let aProps = Object.getOwnPropertyNames(a) let bProps = Object.getOwnPropertyNames(b) // 判断两个对象键值数组长度是否一致,不一致返回false if (aProps.length !== bProps.length) return false // 遍历对象的键值 for (let prop in a) { // 判断a的键值,在b中是否存在,不存在,返回false if (b.hasOwnProperty(prop)) { // 判断a的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回false if (typeof a[prop] === 'object') { if (!isObjectValueEqual(a[prop], b[prop])) return false } else if (a[prop] !== b[prop]) { return false } } else { return false } } return true } console.log(isObjectValueEqual(obj1, obj2)) // false
22.setTimeout和setInterval的区别
setTimeout和setInterval都属于JS中的定时器(setTimeout准确的来说是延时器),可以规定延迟时间再执行某个操作,不同的是setTimeout在规定时间后执行完某个操作就停止了,而setInterval则可以一直循环下去
用法:
function hello(){
alert("hello");
}
//使用方法名字执行方法
var t1 = window.setTimeout(hello,1000);
var t2 = window.setTimeout("hello()",3000);//使用字符串执行方法
window.clearTimeout(t1);//清除定时器
setInterval(hello,1000);
setInterval('hello()',1000);
面试题:
var timer=setTimeout(function(timer){
console.log(timer);
timer+=1;
console.log(timer);
},0)
console.log(timer);
请问以上代码输出结果是?
var timer=setTimeout(function(timer){
console.log(timer);//undefined
timer+=1;
console.log(timer);//NaN
},0)
console.log(timer); //1
第一个为undefined是因为timer为参数,相当于声明了,但没有赋值,所以为undefined;
第二个为NaN是因为undefined+1不是一个数字,所以显示NaN;
第三个为1,是指定时器的返回值,一般认为是id值,每个定时器都有一个对应的id值, 将这个ID值传递给clearInterval(),clearTimeout() 可以取消定时器的执行;