一些面试常见的JavaScript基础知识,内容简单,容易理解,熟记就行,被问到的话要尽量回答准确和全面。
1.JavaScript中数组操作常用方法
- 数组检测: Array.isArray(item)
- 转换为字符串: arrs.join(',')
- 栈方法: 栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构
// push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度
// pop()方法则从数组末尾移除最后一项,减少数组的length值,然后返回移除的项
var colors=new Array();//创建一个数组
var count=colors.push("red","green");//推入两项
alert(count);//2
alert(colors);//["red","green"]
count=colors.push("black");//推入另一项
alert(count);//3
var item=colors.pop();//移除最后一项,并返回最后一项的值
alert(item);//“black”
alert(colors.length);//2
// 以上代码首先我们使用push()将两个字符串推入数组的末尾,并将返回的结果保存在变量count中。
// 然后再推入一个值,而结果任然保存在count中.因为此时数组中包含3项,所以push()返回3。
// 在调用pop()时,它会返回数组的最后一项,即字符串"black".此后,数组中仅剩两项。
- 队列方法: 队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出).
// shift()移除数组中第一个项并返回改项,同时将数组长度减1
// unshift()在数组前端添加任意个项并返回新数组的长度
var n=new Array("张三","李四");
n.push("王五");// 添加参数到数组末尾,并修改数组长度
n.shift();// 移除数组第一项,并返回该项
n.unshift("小钱");//添加参数到数组前端
n.pop();// 从数组末尾移除最后一项
//循环显示出数组的值
for(var i=0;i<n.length;i++) {
console.log(n[i]);// 小钱 李四
}
- 排序方法
var values = [0,1,5,10,15];
values.sort();//进行排序显示
alert(values);//0,1,10,15,5
function compare(value1, value2) {
return value1 - value2;
}
var values = [0,1,5,10,15];
values.sort(compare);
console.log(values); //0,1,5,10,15
- 操作方法
// concat()方法可以基于当前数组中的所有项创建一个新数组
var colors=["red","green","blue"];
var color2=colors.concat("yellow",["black","brown"]);
alert(color2);//red,green,blue,yellow,black,brown
// slice()方法基于当前数组中的一或多个项创建一个新数组
var colors=["red","green","blue","yellow","black"];
colors.slice(1);//green,blue,yellow,black
colors.slice(1,4)//green,blue,yellow
// splice()方法恐怕要算是最强大的数组方法了,它有很多种用法(删除、插入和替换)
var colors=["red","green","blue"];
var removed=colors.splice(0,1);//删除第一项
alert(colors);//green,blue
alert(removed);//red,返回的数组中只有一项
removed=colors.splice(1,0,"yellow","orange");//从位置1开始插入2条数据
alert(colors);//green,yellow,orange,blue
alert(removed);//返回一个空数组
removed=colors.splice(1,1,"red","purple");//从位置1插入2条数据,并删除位置1的数据
alert(colors);//green,red,purple,orange,blue
alert(removed);//yellow,返回的数组中只包含一项
- 位置方法
// indexOf():从数组的开头(位置0)开始向后查找
// lastIndexOf():从数组的末尾开始向前查找
var numbers=[1,2,3,4,5,4,3,2,1];
alert(numbers.indexOf(4));//3 返回第一个出现的4位置
alert(numbers.lastIndexOf(4));//5 返回最后一个出现的4位置
alert(numbers.indexOf(4,4));//5 从位置4开始查找第一个4出现的位置
alert(numbers.lastIndexOf(4,4));//3 从位置4开始查找最后一个4出现的位置
var person={name:"ToNi"};
var people=[{name:"ToNi"}];
var morePeople=[person];
alert(people.indexOf(person));//返回-1 必须严格相等,不仅仅是值相等
alert(morePeople.indexOf(person));//返回0
2.JavaScript如何设置获取盒模型对应的宽和高
- dom.style.width/height(只能获取到内联样式的宽和高,输出值带单位)
- dom.currentStyle.width/height(只有IE浏览器支持,输出值带单位)
- window.getComputedStyle(dom).width/height(兼容性好,输出值带单位)
- dom.getBoundingClientRect().width/height(兼容性好,输出值不带单位)
3.DOM事件的级别
- (1)DOM0: element.onclick = function(){}
- (2)DOM2: element.addEventListener('click',function(){},false)
- (3)DOM3: element.addEventListener('keyup',function(){},false)
- (4)建议结合红宝书第6页
【拓展】
所谓的0级dom与2级dom事件就是不同版本间的差异,具体的说就是,对于不同的dom级别,如何定义事件处理,以及使用时有什么不同。 比如在dom0级事件处理中,后定义的事件会覆盖前面的,但是dom2级事件处理中,对一个按钮点击的时间处理就没有被覆盖掉。
4.DOM事件模型
(1)冒泡型事件处理模型(Bubbling): 冒泡型事件处理模型在事件发生时,首先在最精确的元素上触发,然后向上传播,直到根节点,反映到DOM树上就是事件从叶子节点传播到根节点。
(2)捕获型事件处理模型(Captrue): 相反地,捕获型在事件发生时首先在最顶级的元素上触发,传播到最低级的元素上,在DOM树上的表现就是由根节点传播到叶子节点。
【捕获事件的具体流程】
window==>document==>html==>body==>父级元素==>目标元素
(3)标准的事件处理模型分为三个阶段:
- 父元素中所有的捕捉型事件(如果有)自上而下地执行
- 目标元素的冒泡型事件(如果有)
- 父元素中所有的冒泡型事件(如果有)自下而上地执行
5.Event对象的常见应用
- (1)event.preventDefault(): 阻止事件的默认行为
- (2)event.stopPropagation(): 阻止事件的进一步传播,也就是阻止冒泡
- (3)event.stopImmediatePropagation(): 阻止剩余的事件处理函数的执行,并防止当前事件在DOM树上冒泡。
- (4)event.currentTarget: 返回绑定事件的元素
- (5)event.target: 返回触发事件的元素
6.JavaScript类的声明
// 1、类的声明
function Animal() {
this.name = "name";
}
// 2、ES6中类的声明
class Animal2 {
constructor() {
this.name = name;
}
}
// 3、实例化类
console.log(new Animal(), new Animal2());
7.创建对象有几种方法
(1)字面量:
var k1 = {
name: 'k1'
};
var k2 = new Object({
name: 'k2'
});
(2)通过构造函数:
var m = function (name) {
this.name = name;
};
var k3 = new m('k3');
(3)通过Object.create:
var p = {
name: 'ppp'
};
var k4 = Object.create(p);
8.JavaScript异步加载的方式
- (1)动态脚本加载;
- (2)defer
- (3)async
9.JavaScript的typeof返回哪些数据类型?
- object
- number
- function
- boolean
- underfind
- string
console.log(typeof undefined); // undefined
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof Symbol("foo")); // symbol
console.log(typeof 2172141653n); // bigint
console.log(typeof function () {}); // function
// 不能判别
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
【拓展】
- 比较混淆的是:介绍JavaScript的基本数据类型
- 答案为:Undefined、Null、Boolean、Number、String、BigInt(可以表示任意大小的整数)、Symbol(创建后独一无二且不可变的数据类型)
10.window.onload和DOMContentLoaded的区别是
window.addEventListener('load',function(){
//页面的全部资源加载完才会执行,包括图片、视频等
})
document.addEventListener('DOMContentLoaded',function(){
//DOM渲染完即可执行,此时图片、视频还可能没有加载完
})
11.JavaScript中有哪些内置函数,与内置对象的区别是什么
- (1)内置函数: 是浏览器内核自带的,不用任何函数库引入就可以直接使用的函数,如常规函数(alert等)、数组函数(reverse等)、日期函数(getDate等)、数学函数(floor等)、字符串函数(length等);
- (2)内置对象: 是浏览器本身自带的,内置对象中往往包含了内置函数。内置对象有Object、Array、Boolean、Number、String、Function、Date、RegExp等。
12.JavaScript变量按照存储方式区分为哪些类型,并描述其特点
//值类型
//变量的交换,按值访问,操作的是他们实际保存的值。
//等于在一个新的地方按照新的标准开了一个空间(栈内存),
//这样a的值对b的值没有任何影响
var a = 100;
var b = a;
b = 200;
console.log("a:" + a + ",b:" + b); //a:100,b:200
//引用类型
//变量的交换,当查询时,我们需要先从栈中读取内存地址,
//然后再顺藤摸瓜地找到保存在堆内存中的值;
//发现当复制的是对象,那么obj1和obj2两个对象被串联起来了,
//obj1变量里的属性被改变时候,obj2的属性也被修改。
var obj1 = {
x: 100
};
var obj2 = obj1;
obj2.x = 200;
console.log("obj1:" + obj1.x + ",obj2:" + obj2.x); //obj1:200,obj2:200
13.如何理解JSON
- (1)JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小,比如:
{"age":"12", "name":"back"} - (2)JSON字符串转换为JSON对象:
var obj =eval('('+ str +')');
var obj = str.parseJSON();
var obj = JSON.parse(str);
- (3)JSON对象转换为JSON字符串:
var last=obj.toJSONString();
var last=JSON.stringify(obj);
14.JavaScript中&&和||
- (1)只要“||”前面为false,不管“||”后面是true还是false,都返回“||”后面的值;
- (2)只要“||”前面为true,不管“||”后面是true还是false,都返回“||”前面的值。
- (3)且在js逻辑运算中,0、”“、null、false、undefined、NaN都会判为false,其他都为true。
- (4)只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值;
- (5)只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值。
15.什么是构造函数
(1)构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象。
(2)注意事项:
- 默认函数首字母大写;
- 构造函数并没有显示返回任何东西。new 操作符会自动创建给定的类型并返回他们,当调用构造函数时,new会自动创建this对象,且类型就是构造函数类型;
- 也可以在构造函数中显示调用return.如果返回的值是一个对象,它会代替新创建的对象实例返回。如果返回的值是一个原始类型,它会被忽略,新创建的实例会被返回;
- 因为构造函数也是函数,所以可以直接被调用,但是它的返回值为undefine,此时构造函数里面的this对象等于全局this对象。this.name其实就是创建一个全局的变量name。在严格模式下,当你补通过new 调用Person构造函数会出现错误;
16.函数声明和函数表达式的区别
//成功
fn()
function fn() {
console.log("函数声明,全局,有变量提升的能力");
}
//报错
fn1()
var fun1 = function () {
console.log("函数表达式,局部,没有变量提升的能力")
}
17.说一下对变量提升的理解
(1)顾名思义,就是把下面的东西提到上面。在JS中,就是把定义在后面的东东(变量或函数)提升到前面中定义。
var v = 'Hello World';
(function () {
alert(v); //undefined
var v = 'I love you';
})()
(2)根据上面变量提升原件以及js的作用域(块级作用域)的分析,得知 上面代码真正变成如下:
var v = 'Hello World';
(function () {
var v;
alert(v);
v = 'I love you';
})()
(3)在我们写js code 的时候,我们有2中写法,一种是函数表达式,另外一种是函数声明方式。我们需要重点注意的是,只有函数声明形式才能被提升。
//成功
function myTest() {
foo();
function foo() {
alert("我来自 foo");
}
}
myTest();
//失败
function myTest() {
foo();
var foo = function foo() {
alert("我来自 foo");
}
}
myTest();
18.说一下this几种不同的使用场景
(1)作为构造函数执行,如果函数创建的目的是使用new来调用,并产生一个对象,那么此函数被称为构造器函数;
var Niu = function (string) {
this.name = string;
};
(2)作为对象属性执行,对象成员方法中的this是对象本身,此时跟其他语言是一致的,但是也有差异,JavaScript中的this到对象的绑定发生在函数调用的时候;
var myObj = {
value: 0,
increment: function (inc) {
this.value += typeof inc === 'number' ? inc : 1;
}
};
myObj.increment(); //1
myObj.increment(2); //3
(3)作为普通函数执行,以普通方式定义的函数中的this:会被自动绑定到全局对象;
var value = 232;
function toStr() {
console.log(this.value);
}
(4)对象方法中闭包函数的this
//在以普通方式定义的函数中的this会被自动绑定到全局对象上,
//大家应该可以看出闭包函数定义也与普通方式无异,因此他也会被绑定到全局对象上。
value = 10;
var closureThis = {
value: 0,
acc: function () {
var helper = function () {
this.value += 2;
console.log("this.value : %d", this.value);
}
helper();
}
};
closureThis.acc(); //12
closureThis.acc(); //14
var closureThat = {
value: 0,
acc: function () {
that = this;
var helper = function () {
that.value += 2;
console.log("that.value : %d", that.value);
}
helper();
}
};
closureThat.acc(); // 2
closureThat.acc(); // 4
(5)apply函数的参数this,apply方法允许我们选择一个this值作为第一个参数传递,第二个参数是一个数组,表明可以传递多个参数。
19.什么是自由变量
自由变量就是当前作用域没有定义的变量,即“自由变量”
var a = 100;
function F1() {
var b = 200;
function F2() {
var c = 300;
//a是自由变量
console.log(a);
//b是自由变量
console.log(b);
console.log(c);
}
F2();
}
F1();
20.同步和异步的区别是什么?分别举一个同步和异步的例
同步会阻塞代码执行,而异步不会;alert是同步,setTimeout是异步。
21.JavaScript中的异步和单线程
- (1)其实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。
- (2)js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。
22.什么是跨域
- (1)浏览器有同源策略,不允许ajax访问其他域接口。跨域条件(协议、域名、端口)有一个不同就算是跨域。
- (2)可以跨域的三个标签(
<img src=xxx>、<link href=xxx>、<script src=xxx>)。三个标签的应用场景(<img>用于打点统计,统计网站可能是其他域、<link><script>可以使用CDN,CDN的也是其他域、<script>可以用于JSOP)。 - (3)跨域注意的事项:所有的跨域请求都必须经过信息提供方允许、如果未经过允许即可获取,那是浏览器同源策略出现漏洞。
23.解决跨域的几种方案
- (1)通过jsonp跨域:在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。
- (2)通过修改document.domain来跨子域
- (3)使用window.name来进行跨域
- (4)使用HTML5中新引进的window.postMessage方法来跨域传送数据
24.null,undefined 的区别
- (1)null:表示一个对象是“没有值”的值,也就是值为“空”;
- (2)undefined:表示一个变量声明了没有初始化(赋值);
【注意】
在验证null时,一定要使用 === ,因为 == 无法分别 null 和undefined
25.JavaScript的作用域链式什么
全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,直至全局函数,这种组织形式就是作用域链。
26.Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是
(1)javaScript中hasOwnProperty函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员。
(2)使用方法:
- object.hasOwnProperty(proName)
- 其中参数object是必选项。一个对象的实例。
- proName是必选项。一个属性名称的字符串值。
- 如果 object 具有指定名称的属性,那么JavaScript中hasOwnProperty函数方法返回 true,反之则返回 false。
27.异步加载JS的方式有哪些
- (1)defer,只支持IE;
- (2)async:
- (3)创建script,插入到DOM中,加载完毕后callBack
28.ajax是什么?同步和异步的区别?
- (1)ajax是一种无需重新加载整个网页的情况下,能够更新部分网页的技术。
- (2)同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
29.事件委托是什么,举个例子
它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。那这是什么意思呢?网上的各位大牛们讲事件委托基本上都用了同一个例子,就是取快递来解释这个现象。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<style>
ul li {
display: block;
height: 50px;
width: 100px;
text-align: center;
line-height: 50px;
background-color: #888;
margin-bottom: 8px;
}
</style>
</head>
<body>
<ul id="Inul">
<li id="item1">111</li>
<li id="item2">222</li>
<li id="item3">333</li>
<li id="item4">444</li>
<li id="item5">555</li>
</ul>
<script>
//不使用事件委托
/*window.onload = function(){
var oUL = document.getElementById("Inul");
var aLi = oUL.getElementsByTagName("li");
for(var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){
alert(123);
}
}
}*/
//使用事件委托
window.onload = function () {
var oUL = document.getElementById("Inul");
oUL.onclick = function (e) {
console.log(e)
console.log(e.target.nodeName)
console.log(e.target.id)
alert(123);
}
}
</script>
</body>
</html>
使用事件委托的意义是为了减少代码的DOM操作,提高程序性能。
30.如何实现JavaScript模块化,请至少使用两种方法
- (1)立即执行函数(IIFE): 可以达到不暴露私有成员的目的。
- (2)对象写法: 可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
31.高阶函数是什么,怎么去写一个高阶函数
- 高阶函数: 参数值为函数或者返回值为函数。例如map,reduce,filter,sort方法就是高阶函数。
- 编写高阶函数,就是让函数的参数能够接收别的函数。
32.typeof 是否能正确判断类型?
typeof 对于原始类型来说,除了 null 都可以显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
33.instanceof 能正确判断对象的原理是什么?
如果我们想判断一个对象的正确类型,这时候可以考虑使用 instanceof,因为内部机制是通过原型链来判断的
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true
var str = 'hello world'
str instanceof String // false
var str1 = new String('hello world')
str1 instanceof String // true
对于原始类型来说,你想直接通过 instanceof 来判断类型是不行的,当然我们还是有办法让 instanceof 判断原始类型的
class PrimitiveString {
static [Symbol.hasInstance](x) {
return typeof x === 'string'
}
}
console.log('hello world' instanceof PrimitiveString) // true
你可能不知道 Symbol.hasInstance 是什么东西,其实就是一个能让我们自定义 instanceof 行为的东西,以上代码等同于 typeof 'hello world' === 'string',所以结果自然是 true 了。这其实也侧面反映了一个问题, instanceof 也不是百分之百可信的。
34.数组转字符串以及字符串转数组的方法
// 字符串转数组
var str = "abcdef";
console.log(str.split("")); // ['a', 'b', 'c', 'd', 'e', 'f']
// 数组转字符串
var arr = [1,3,4,56,2];
console.log(arr.join("")); // 134562
35.箭头函数与普通函数function的区别是什么?
- 箭头函数语法上比普通函数更加简洁(ES6中每一种函数都可以使用形参赋默认值和剩余运算符)
function fn(x){
return function(y){
return x+y
}
}
let fn = x => y => x+y
- 箭头函数没有自己的this,它里面出现的this是继承函数所处上下文的this。(使用call/apply等任何方式都无法改变this的指向)
let obj = {
name: 'hah'
}
function fn1(){
console.log(this)
}
fn1.call(obj) //{name: 'hah'}
let fn2 = ()=>{
console.log(this)
}
fn2.call(obj) //window
document.body.onclick = function() {
console.log(this) // body
}
document.body.onclick = () => {
console.log(this) // window
}
- 箭头函数没有arguments(类数组),智能基于...arg获取传递的参数集合(数组)
let fn = (...arg)=>{
console.log(arguments) //arguments is not defined
console.log(arg) //[10,20,30]
}
fn(10,20,30)
- 箭头函数不能使用new生成实例,因为自身没有this和prototype
36.==和=== 一般是在什么情况下使用?
除了 == null 之外,其他都一律使用 === ,例如
const obj = { x: 100 }
if(obj.a == null) {}
// 相当于
if(obj.a === null || obj.a === undefined) {}
37.箭头函数需要注意的地方
当要求动态上下文的时候,就不能够使用箭头函数,也就是this的固定化。
- 1、在使用=>定义函数的时候,this的指向是定义时所在的对象,而不是使用时所在的对象;
- 2、不能够用作构造函数,这就是说,不能够使用new命令,否则就会抛出一个错误;
- 3、不能够使用arguments对象;
- 4、不能使用yield命令;
下面来看一道面试题,重点说明下第一个知识点:
class Animal {
constructor() {
this.type = "animal";
}
say(val) {
setTimeout(function () {
console.log(this); //window
console.log(this.type + " says " + val);
}, 1000)
}
}
var animal = new Animal();
animal.say("hi"); //undefined says hi
【拓展】 《JavaScript高级程序设计》第二版中,写到:“超时调用的代码都是在全局作用域中执行的,因此函数中this的值在非严格模式下指向window对象,在严格模式下是undefined”。也就是说在非严格模式下,setTimeout中所执行函数中的this,永远指向window!!
我们再来看看箭头函数(=>)的情况:
class Animal {
constructor() {
this.type = "animal";
}
say(val) {
setTimeout(() => {
console.log(this); //Animal
console.log(this.type + ' says ' + val);
}, 1000)
}
}
var animal = new Animal();
animal.say("hi"); //animal says hi
【特点】
- 不需要function关键字来创建函数
- 省略return关键字
- 继承当前上下文的 this 关键字
38.let和const
let是更完美的var,不是全局变量,具有块级函数作用域,大多数情况不会发生变量提升。const定义常量值,不能够重新赋值,如果值是一个对象,可以改变对象里边的属性值。
- 1、let声明的变量具有块级作用域
- 2、let声明的变量不能通过window.变量名进行访问
- 3、形如for(let x..)的循环是每次迭代都为x创建新的绑定
下面是var带来的不合理场景
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[5]() //10,a[5]输出f(){console.log(i);},后面加个括号代表执行f()
在上述代码中,变量i是var声明的,在全局范围类都有效,所以用来计数的循环变量泄露为全局变量。所以每一次循环,新的i值都会覆盖旧值,导致最后输出都是10。
而如果对循环使用let语句的情况,那么每次迭代都是为x创建新的绑定代码如下:
var arr = [];
for (let i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[5]() //5,a[5]输出f(){console.log(i);},后面加个括号代表执行f()
【拓展】 当然,除了这种方式让数组找中的各个元素分别是不同的函数,我们还可以采用ES5中的闭包和立即函数两种方法。
1、采用闭包
function showNum(i) {
return function () {
console.log(i)
}
}
var a = []
for (var i = 0; i < 5; i++) {
a[i] = showNum(i)(); //循环输出1,2,3,4
}
2、采用立即执行函数
var a = []
for (var i = 0; i < 5; i++) {
a[i] = (function (i) {
return function () {
console.log(i)
}
})(i)
}
a[2](); //2
【面试】 把以下代码使用两种方法,依次输出0-9
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i)
})
}
funcs.forEach(function (func) {
func(); //输出十个10
})
方法一:使用立即执行函数
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push((function (value) {
return function () {
console.log(value)
}
}(i)))
}
funcs.forEach(function (func) {
func(); //依次输出0-9
})
方法二:使用闭包
function show(i) {
return function () {
console.log(i)
}
}
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push(show(i))
}
funcs.forEach(function (func) {
func(); //0 1 2 3 4 5 6 7 8 9
})
方法三:使用let
var funcs = []
for (let i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i)
})
}
funcs.forEach(function (func) {
func(); //依次输出0-9
})
其他知识点:forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。戳这里查看参考文章
39.Set数据结构
es6方法,Set本身是一个构造函数,它类似于数组,但是成员值都是唯一的。
const set = new Set([1,2,3,4,4])
console.log([...set] )// [1,2,3,4]
console.log(Array.from(new Set([2,3,3,5,6]))); //[2,3,5,6]
40.Class的讲解
class语法相对原型、构造函数、继承更接近传统语法,它的写法能够让对象原型的写法更加清晰、面向对象编程的语法更加通俗,下面是class的具体用法。
class Animal {
constructor() {
this.type = 'animal'
}
says(say) {
console.log(this.type + 'says' + say)
}
}
let animal = new Animal()
animal.says('hello') // animal says hello
class Cat extends Animal {
constructor() {
super()
this.type = 'cat'
}
}
let cat = new Cat()
cat.says('hello') // cat says hell
可以看出在使用extend的时候结构输出是cat says hello 而不是animal says hello。说明contructor内部定义的方法和属性是实例对象自己的,不能通过extends 进行继承。在class cat中出现了super(),这是什么呢?因为在ES6中,子类的构造函数必须含有super函数,super表示的是调用父类的构造函数,虽然是父类的构造函数,但是this指向的却是cat。更详细的参考文章
41.模板字符串
就是这种形式${varible},在以往的时候我们在连接字符串和变量的时候需要使用这种方式'string' + varible + 'string'但是有了模版语言后我们可以使用string${varible}string这种进行连接。基本用途有如下:
1、基本的字符串格式化,将表达式嵌入字符串中进行拼接,用${}来界定。
//es5
var name = 'lux';
console.log('hello' + name);
//es6
const name = 'lux';
console.log(`hello ${name}`); //hello lux
2、在ES5时我们通过反斜杠()来做多行字符串或者字符串一行行拼接,ES6反引号(``)直接搞定。
//ES5
var template = "hello \
world";
console.log(template); //hello world
//ES6
const template = `hello
world`;
console.log(template); //hello 空行 world
【拓展】 字符串的其他方法
// 1.includes:判断是否包含然后直接返回布尔值
let str = 'hahay'
console.log(str.includes('y')) // true
// 2.repeat: 获取字符串重复n次
let s = 'he'
console.log(s.repeat(3)) // 'hehehe'
42.什么是变量提升?什么是暂时性死区?var、let 及 const 区别?
对于这个问题,我们应该先来了解提升(hoisting)这个概念。
console.log(a) // undefined
var a = 1
从上述代码中我们可以发现,虽然变量还没有被声明,但是我们却可以使用这个未被声明的变量,这种情况就叫做提升,并且提升的是声明。对于这种情况,我们可以把代码这样来看
var a
console.log(a) // undefined
a = 1
接下来我们再来看一个例子
var a = 10
var a
console.log(a)
对于这个例子,如果你认为打印的值为 undefined 那么就错了,答案应该是 10,对于这种情况,我们这样来看代码
var a
var a
a = 10
console.log(a)
到这里为止,我们已经了解了 var 声明的变量会发生提升的情况,其实不仅变量会提升函数也会被提升。
console.log(a) // ƒ a() {}
function a() {}
var a = 1
对于上述代码,打印结果会是 ƒ a() {},即使变量声明在函数之后,这也说明了函数会被提升,并且优先于变量提升。
说完了这些,想必也知道 var 存在的问题了,使用 var 声明的变量会被提升到作用域的顶部,接下来我们再来看 let 和 const 。我们先来看一个例子:
var a = 1
let b = 1
const c = 1
console.log(window.b) // undefined
console.log(window.c) // undefined
function test(){
console.log(d) // Uncaught ReferenceError: Cannot access 'd' before initialization
let d
}
test()
首先在全局作用域下使用 let 和 const 声明变量,变量并不会被挂载到 window 上,这一点就和 var 声明有了区别。再者当我们在声明 d 之前如果使用了 d,就会出现报错的情况。
你可能会认为这里也出现了提升的情况,但是因为某些原因导致不能访问。
首先报错的原因是因为存在暂时性死区,我们不能在声明前就使用变量,这也是 let 和 const 优于 var 的一点。然后这里你认为的提升和 var 的提升是有区别的,虽然变量在编译的环节中被告知在这块作用域中可以访问,但是访问是受限制的。
那么到这里,想必大家也都明白 var、let 及 const 区别了,不知道你是否会有这么一个疑问,为什么要存在提升这个事情呢,其实提升存在的根本原因就是为了解决函数间互相调用的情况
function test1() {
test2()
}
function test2() {
test1()
}
test1()
假如不存在提升这个情况,那么就实现不了上述的代码,因为不可能存在 test1 在 test2 前面然后 test2 又在 test1 前面。下面来个总结
- 函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部
- var 存在提升,我们能在声明之前使用。let、const 因为暂时性死区的原因,不能在声明前使用
- var 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会
- let 和 const 作用基本一致,但是后者声明的变量不能再次赋值
43.数据类型的判断有哪几种方法
- typeof:能判断所有值类型,函数。不可对 null、对象、数组进行精确判断,因为都返回
object。
console.log(typeof undefined); // undefined
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof Symbol("foo")); // symbol
console.log(typeof 2172141653n); // bigint
console.log(typeof function () {}); // function
// 不能判别
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
- instanceof:能判断对象类型,不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。比如考虑以下代码:
class People {}
class Student extends People {}
const vortesnail = new Student();
console.log(vortesnail instanceof People); // true
console.log(vortesnail instanceof Student); // true
// 其实现就是顺着原型链去找,如果能找到对应的 `Xxxxx.prototype` 即为 `true` 。比如这里的
// `vortesnail` 作为实例,顺着原型链能找到 `Student.prototype` 及 `People.prototype` ,所以都为 `true` 。
- Object.prototype.toString.call() :所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等。
Object.prototype.toString.call(2); // "[object Number]"
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(function () {}); // "[object Function]"
44.如何判断变量是否为数组?
Array.isArray(arr); // true
arr.__proto__ === Array.prototype; // true
arr instanceof Array; // true
Object.prototype.toString.call(arr); // "[object Array]"