JavaScript基础面试题

200 阅读15分钟

1.延迟加载JS有哪些方式?

1.延迟加载js: async defer

 <script defer type="text/javascript" src="./script.js"></script>

2.defer: 等HTML全部解析完成,才会执行js代码,顺序执行

3.async:async和html解析同步(一起的),不是顺序执行代码(谁先加载完先执行)

image.png

4.没有加async和defer

image.png

5.添加defer

HTML解析过程中会下载js资源

image.png

6.添加async

image.png

7.我什么时候应该使用什么?

通常,您希望尽可能使用,然后不使用属性。以下是一些需要遵循的一般规则:async defer

  • 如果脚本是模块化的并且不依赖于任何脚本,则使用 。async
  • 如果脚本依赖于另一个脚本或被另一个脚本所依赖,则使用 。defer
  • 如果脚本很小并且被脚本所依赖,则使用在脚本上方没有属性的内联。async script async

2.JS中有哪些数据类型

1.原始数据类型(基本数据类型):

7种:Undefined,Null,Boolean,Number,String,symbol,bigint

2.引用数据类型(复杂数据类型):

Object

3.面试题

1.以下弹出的数据类型 image.png

image.png

2.如下:

        console.log(typeof (NaN));//number
        console.log(typeof (undefined));// undefined
        console.log(typeof (null));//object

3.NaN是一个数值类型,但不是一个具体的数字。

3.null和defined的区别

  1. 作者在设计js的都是先设计的null(为什么设计了null:最初设计js的时候借鉴了java的语言)
  2. null会被隐式转换成0,很不容易发现错误。
  3. 先有null后有undefined,出来undefined是为了填补之前的坑。

具体区别:JavaScript的最初版本是这样区分的:

  • null是一个表示"无"的对象(空对象指针),转为数值时为0;
  • undefined是一个表示"无"的原始值,转为数值时为NaN。

4. ==和===有什么不同?

1. == : 比较的是值

  • string == number || boolean || number ....都会隐式转换
  • 通过valueOf转换(valueOf() 方法通常由 JavaScript 在后台自动调用,并不显式地出现在代码中,返回对象的原始值) image.png
<script>
        console.log(1 == '1');//true
        console.log(true == 1);//true
        console.log(null == undefined);//true
        console.log([1, 2] == '1,2');//true
        let s = '2';
        if (s == 2) {
            console.log(typeof s);//string
        }
</script>

注: JS 中 valueOf() 方法的详解

  1. JavaScript 中的 valueOf() 方法用于返回指定对象的原始值,若对象没有原始值,则将返回对象本身。通常由JavaScript内部调用,而不是在代码中显式调用。
  2. 默认情况下,valueOf 方法由 Object 后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。
  3. JavaScript 的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的 valueOf() 方法的返回值和返回值类型均可能不同。

2.=== : 除了比较值,还比较类型

  • 如果类型不同,直接返回false。
  • 写代码尽量用===

5.JS微任务和宏任务

1.基础知识

  1. js是单线程的语言。(同一时间只能做一件事)
  2. js代码执行流程:同步执行完==>事件循环 同步的任务都执行完了,才会执行事件循环的内容 进入事件循环:ajax请求、定时器、事件....
  3. 事件循环中包含:【微任务、宏任务】
  • 微任务:Promise async/await
  • 宏任务:setTimeout setInterval Ajax DOM事件
  • 微任务比宏任务的执行时间要早
  • 要执行宏任务的前提是清空了所有的微任务

流程:同步==>事件循环【微任务和宏任务】==>微任务==>宏任务=>微任务...

2.注意:

宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。

  • 宏任务(Macrotask)大概如下:setTimeout setInterval MessageChannel I/O setImmediate(Node环境) script(整体代码块)
  • 微任务(Microtask)大概如下:MutationObserver(浏览器环境) promise.[ then/catch/finally ] 事件队列 process.nextTick(Node环境)

面试题一:

for(var i=0;i<3;i++){
	setTimeout(function(){
		console.log("先执行同步的,再执行异步的.打印结果:",i);
	},1000*i);
}

结果:

image.png

面试题二:

setTimeout(function(){
	console.log('宏任务1');//宏任务
})

new Promise((resolve)=>{
	console.log( '同步1:promise 1'); //同步
	resolve();
}).then(()=>{
	console.log('微1')
}).then(()=>{
	console.log('微2')
})

console.log("同步2");//同步

结果:

image.png

6.JS作用域考题

1. 除了函数外,js是没有块级作用域。
2. 作用域链:内部可以访问外部的变量,但是外部不能访问内部的变量。
    注意:如果内部有,优先查找到内部,如果内部没有就查找外部的。
3. 注意声明变量是用var还是没有写(window.)
4. 注意:js有变量提升的机制【变量悬挂声明】
5. 优先级:声明变量 > 声明普通函数 > 参数 > 变量提升

面试时怎么看:

  1. 本层作用域有没有此变量【注意变量提升】
  2. 注意:js除了函数外没有块级作用域
  3. 普通声明函数是不看写函数的时间顺序的

1.面试题一

// 2.相当于在全局var b=10;
		(function () {
			var a = b = 10; //1.这b为全局变量,b默认window.b。相当于2.
		})()

		console.log(b);//10

2.面试题二

function c(){
	var b = 1;
	function a(){
		console.log( b );//undefined。js有变量提升的机制【变量悬挂声明】
		var b = 2;
		console.log( b );//2
	}
	a();
	console.log( b );//1
}
c();

3.面试题三

var name = 'a';
(function(){//var name;

	if( typeof name == 'undefined' ){
		var name = 'b';
		console.log('111'+name);//111b
	}else{
		console.log('222'+name);
	}

})()

4.面试题四

console.log( a );//undefined
if( false ){
	var a = 10;
}
console.log( a );//undefined

var bar = 1;
function test(){
	console.log( bar );//undefined
	var bar = 2;
	console.log( bar );//2
}
test();

5.面试题五

优先级:声明变量 > 声明普通函数 > 参数 > 变量提升

//1.声明变量大于声明普通函数
function fun( a ){	
	var a = 10;
	function a(){}//普通的声明函数
	console.log( a );//10
}	
fun( 100 );

//2.函数大于参数
function fun( a ){
	var a = 10;
	var a = function a(){}//声明变量的形式
	console.log( a );//f a(){}

}	
fun( 100 );
//3.函数大于变量提升
function fun(){ // var a 
	console.log( a );//f a(){}
	var a = 20;
	function a(){}
}
fun();

//4.参数大于变量提升
function fun(a){ // var a 
	console.log( a );//a
	var a = 20;
	function a(){}
}
fun(100);

//5.变量的再次赋值
function fun(){ // var a 
	
	a = 10;
	console.log( a );//10
	var a = 20;//变量的再次赋值
	console.log( a );//20
}
fun();

7.JS对象考题

1.js对象注意点

  1. 对象是通过new操作符构建出来的,所以对象之间不相等(除了引用外);
console.log( [1,2,3] === [1,2,3] );//false
  1. 对象注意:引用类型(共同一个地址);
var obj1 = {
	a: 1
	}
	var obj2 = obj1;//obj1对象引用的地址赋值给了obj2
	obj1.a = 'aaaaa';
        console.log(obj1, obj2);//{ a: 'aaaaa' } { a: 'aaaaa' }
        
var arr1 = [1, 2, 3];
var arr2 = arr1;
console.log(arr1 === arr2);//true

image.png 3. 对象的key都是字符串类型;

var o = {
	b: 'bbbbbb'
}
var obj1 = {
         a: 1,
	'张三': '你好'
}
obj1[o] = '123';//给obj1添加一个o进来
for (var k in obj1) {
     console.log(k);
}

结果

image.png

var a = {}
	var b = {
	key: 'a'
}
	var c = {
        	key: 'c'
	}
        
         // [object Object]
     a[b] = '123';//相当于	var a = {[object Object]:123  }
     a[c] = '456';//会覆盖前面的{[object Object]:123 }变成{[object  Object]:456}
		console.log(a[b]); // 456
		for (var k in a) {
		console.log(k)//[object Object]
		console.log(typeof k)//string

		}

  1. 对象如何找属性|方法;

查找规则:先在对象本身找 ===> 构造函数中找 ===> 对象原型中找 ===> 构造函数原型中找 ===> 对象上一层原型查找

image.png 注:new Array是直接创建一个数组,平时使用的时候还能省去new关键词来使用

image.png

function Fun() {
			this.a = '在Fun函数中添加的';
		}
		Fun.prototype.a = '这是Fun原型添加的';
		let obj = new Fun();
		// console.log(Fun.prototype == obj._proto_);
		obj.a = '对象本身';
		obj.__proto__.a = '这是对象原型添加的';
		Object.prototype.a = '这是Object添加的'
 // 1.obj.a 会先找对象本身-没有-->则查找在函数中添加的-->查找对象原型中添加的-->Fun函数中添加的-->对象中添加的
		console.log(obj.a);

2.面试题

var obj1 = {
	a:'hellow'
}
var obj2 = obj1;
obj2.a = 'world';
console.log(obj1); //{a:world}
(function(){
	console.log(a); //undefined
	var a = 1;
})();

8.JS作用域+this指向+原型 考题

1.对于函数的理解

	function fun() {
			// this.xxx = '12334',t添加之后,console.log(new fun())返回1111 和fun {xxx: '12334'}
			console.log("1111");
			return 'aaaa';
		}
		console.log(fun);/*打印的函数体,ƒ fun(){console.log("1111");return 'aaaa';}*/
		console.log(fun());//1111 aaaa
		console.log(new fun())//1111和返回一个对象fun{}

2.调用foo()的结果

function Foo() {
			getName = function () { console.log(1) } //注意getName是全局window.的
			return this;
		}

		Foo.getName = function () { console.log(2) }
		Foo.prototype.getName = function () { console.log(3) }
		var getName = function () { console.log(4) }
		function getName() {
			console.log(5)
		}
		// 1.情况一:加()
		// 二、在这里会执行getName = function () { console.log(1) },覆盖前面的var getName = function () { console.log(4) }
		Foo().getName();  //1  一、,Foo加()就会运行21,22行,返回window。相当于window.getName()

		getName(); //1

3.考题一

	function Foo() {
			getName = function () { console.log(1) } //注意getName是全局window.的
			return this;
		}

		Foo.getName = function () { console.log(2) }
		Foo.prototype.getName = function () { console.log(3) }
		var getName = function () { console.log(4) }
		function getName() {
			console.log(5)
		}
		/*情况二*/
		Foo.getName();    //2
		getName(); 		  //4,声明的变量大于普通函数
		Foo().getName();  //1 
		getName();		  //1
		// 对象查找一个属性方法,现在他本身找,--》构造函数找--》对象的原型找--》构造函数的原型找--》原型链里去找。
                //一、先看new Foo()本身有没有--》在去Foo的构造函数里面找,而foo里的getName是全局window.的,不是this.的--》去对象原型(__proto__)没有,但是对象函数原型和构造函数原型是一个原型,构造函数原型(Foo.prototype.getName = function () { console.log(3) }
		new Foo().getName();//3, 

4.考题二

1.头条考题一

<script type="text/javascript">
//头条考题
var o = {
	a:10,
	b:{
		a:2,
		fn:function(){
			console.log( this.a ); // 2
			console.log( this );   //代表b对象 {a: 2, fn: ƒ}
		}
	}
}
o.b.fn();//b执行的fn,fn里的this代表的b
</script>

2.头条考题二

<script type="text/javascript">
		//头条考题
		window.name = 'ByteDance';
		function A() {
			this.name = 123;
		}
		A.prototype.getA = function () {
			console.log(this);//this代表window
			return this.name + 1;
		}
		let a = new A();
		console.log(a.getA);/*ƒ () {console.log(this);return this.name + 1;}*/
		let funcA = a.getA;//a.getA,返回的是函数体,相当于把函数赋给了funA
		funcA();  //ByteDance1
	</script>

3.头条考题三

<script type="text/javascript">
		//头条考题
		var length = 10;
		function fn() {
			console.log(this);//这里的this代表window
			return this.length + 1;
		}
		var obj = {
			length: 5,
			test1: function () {
				return fn();
			}
		}
		obj.test2 = fn;/*相当于obj.test2 =function(){return this.length + 1;}*/
		// 
		console.log(obj.test1()); //1
		// fn()结果11,obj.test2()结果6
		console.log(fn() === obj.test2()); //false
		// obj.test1()结果为11,obj.test2()结果为6
		console.log(obj.test1() == obj.test2()); //false
	</script>

9.JS判断变量是不是数组,你能写出哪些方法?

1.方式一:isArray

var arr = [1,2,3];
console.log( Array.isArray( arr ) );//true

2.方式二:instanceof 【可写,可不写】

var arr = [1,2,3];
console.log( arr instanceof Array );//true

3.方式三:原型prototype

var arr = [1,2,3];
console.log( Object.prototype.toString.call(arr).indexOf('Array') > -1 );//true

4.方式四:isPrototypeOf()

var arr = [1,2,3];
console.log(  Array.prototype.isPrototypeOf(arr) )//true

5.方式五:constructor

var arr = [1,2,3];
console.log(  arr.constructor.toString().indexOf('Array') > -1 )//true

<script type="text/javascript">
		var str = '你好';
		var arr = [1, 2, 3];
		console.log(Array.isArray(arr));//true
		console.log(arr instanceof Array);//true
		console.log(Object.prototype.toString.call(arr));//[object Array]
		console.log(Object.prototype.toString.call(arr).indexOf('Array') > -1);//true
		console.log(Array.prototype.isPrototypeOf(arr))
		console.log(arr.constructor.toString().indexOf('Array') > -1)
	</script>

10.面试题:slice是干嘛的、splice是否会改变原数组

1. slice是来截取的

从已有的数组中返回选定的元素
提取数组中的某一部分,并以新的字符串返回被提取的部分
slice方法不会改变原始数组

  • 参数可以写slice(3)、slice(1,3)、slice(-3)
  • 返回的是一个新的数组
  • array.slice(start, end)
  1. start 可选,规定从何处开始选取。如果该参数为负数,则表示从原数组中倒数第几个元素开始提取。

  2. end 可选,规定从何处结束选取。该参数是数组片段结束处的数组下标,截取的片段不包含改元素。

  3. 返回值: 返回一个新的数组,包含从start(包括该元素) 到end(不包括该元素)的arrayObject中的元素。

var arr1 = ['a', 'b', 'c', 'd', 'e'];
	var arr2 = arr1.slice(1, 3);//从下标为1开始截取3之间的有元素,不包括下标3的元素
	console.log(arr1);//slice不会改变原数组
	console.log(arr2);//['b', 'c']
	var arr3 = arr1.slice(-3);
	console.log(arr3);//['c', 'd', 'e']

2. splice 功能有:插入、删除、替换

  • 返回:删除的元素
  • 该方法会改变原数组
var arr3 = ['a', 'b', 'c', 'd', 'e'];
		var arr4 = arr3.splice(1, 1);
		console.log("arr4", arr4);////['b']返回删除到哪里的内容
		console.log("arr3", arr3);//['a', 'c', 'd', 'e']

		var arr3 = ['a', 'b', 'c', 'd', 'e'];
		var arr4 = arr3.splice(1, 1, '你好');//从索引为1的位置,删除1个,插入'你好'
		console.log(arr4, arr3);//['b']['a', '你好', 'c', 'd', 'e']

11.JS数组去重

1.方式一:new Set()

因为Set数据结构并非真正的数组,它类似于数组,并且成员值都是唯一的,没有重复,所以可以用来做去重操作。但是因为它是一个类似数组结构,所以需要转型为真正的数组去使用。所以需要用Array.from。

var arr1 = [1, 2, 3, 2, 4, 1];
		console.log(new Set(arr1));//Set(4) {1, 2, 3, 4}
		console.log(Array.from(new Set(arr1)));//Array.from把像数组的内容转化成数组
		console.log([...new Set(arr1)]);//扩展运算符展开
		// 封装成函数
		function unique(arr) {
			return [...new Set(arr)]
		}
		console.log(unique(arr1));//[1, 2, 3, 4]

2.方式二:indexOf

var arr2 = [1, 2, 3, 2, 4, 1];
		function unique(arr) {
			var brr = [];
			for (var i = 0; i < arr.length; i++) {
				if (brr.indexOf(arr[i]) == -1) {//如果没有找到,把arr[i]的内容push到brr中
					brr.push(arr[i]);
				}
			}
			return brr;
		}
		console.log(unique(arr2));////[1, 2, 3, 4]

3.方式三:sort

	var arr3 = [1, 2, 3, 2, 4, 1];
		function unique(arr) {
			arr = arr.sort();//将数组排序
			var brr = [];
			for (var i = 0; i < arr.length; i++) {
				if (arr[i] !== arr[i - 1]) {
					brr.push(arr[i]);
				}
			}
			return brr;
		}
		console.log(unique(arr3));//[1, 2, 3, 4]

12.找出多维数组最大值

1.forEach()  方法对数组的每个元素执行一次提供的函数。

image.png


	<script type="text/javascript">
		function fnArr(arr) {
			var newArr = [];
			// forEach() 方法对数组的每个元素执行一次提供的函数。
			arr.forEach((item, index) => {
				// 扩展运算符(...)是ES6的语法,用于取出参数对象的所有可遍历属性,然后拷贝到当前对象之中。
				newArr.push(Math.max(...item))
			})

			return newArr;
		}
		console.log(fnArr([
			[4, 5, 1, 3],
			[13, 27, 18, 26],
			[32, 35, 37, 39],
			[1000, 1001, 857, 1]
		]));//[5, 27, 39, 1001]
	</script>

13.面试题:给字符串新增方法实现功能

1.题目:

给字符串对象定义一个addPrefix函数,当传入一个字符串str时,它会返回新的带有指定前缀的字符串,例如:

console.log( 'world'.addPrefix('hello') ) 控制台会输出helloworld

//一、
	var str = '你好'
		String.prototype.addPrefix = function (str) {
			console.log(this);//String {'你好'}
			// return str + this;
		}

		console.log(str.addPrefix())
                
//二
    	var str = '你好'
		String.prototype.addPrefix = function (str) {
			console.log(this);//这里的this指的是{'world'}
			return str + this;
		}

		console.log('world'.addPrefix('hello'))//helloworld

14.面试题:找出字符串出现最多次数的字符以及次数

var str = 'aaabbbbbccddddddddddx';
		var obj = {};
		for (var i = 0; i < str.length; i++) {
			var char = str.charAt(i);//charAt()	返回指定索引位置的字符
			console.log(char);
			if (obj[char]) {
				obj[char]++;
			} else {
				obj[char] = 1;
			}
		}
		console.log(obj);//{a: 3, b: 5, c: 2, d: 10, x: 1}
		//统计出来最大值
		var max = 0;
		for (var key in obj) {
			if (max < obj[key]) {
				max = obj[key];
			}
		}
		//拿最大值去对比
		for (var key in obj) {
			if (obj[key] == max) {
				console.log('最多的字符是' + key);//d
				console.log('出现的次数是' + max);//10
			}
		}

15.面试题:new操作符具体做了什么

  1. 创建了一个空的对象
  2. 将空对象的原型,指向于构造函数的原型
  3. 将空对象作为构造函数的上下文(改变this指向)
  4. 对构造函数有返回值的处理判断
function Foo(){
	// console.log(this);
	this.name = '张三'
	// return [1,2,3];//如果返回的是基本类型,则忽略,如果返回的是引用类型,如:对象,数组,则直接返回
}
console.log(Foo());//运行Foo(),Foo里面的this执行window
// Foo();
// 1. 创建了一个空的对象
console.log( new Foo() );//new Foo()之后,Foo里面的this指向Foo {}对象
// 2.将空对象的原型,指向于构造函数的原型
console.log( Foo.prototype == new Foo().__proto__ );//true


function Fun( age,name ){
	this.age = age;
	this.name = name;
}
function create( fn , ...args ){
	//1. 创建了一个空的对象
	var obj = {}; //var obj = Object.create({})
	//2. 将空对象的原型,指向于构造函数的原型
	// Object.setPrototypeOf(),为现有对象设置原型,返回一个新对象
    // 接收两个参数:第一个是现有对象,第二是原型对象。
	Object.setPrototypeOf(obj,fn.prototype);
	//3. 将空对象作为构造函数的上下文(改变this指向)
	// apply作用 作用有两个,跟它的入参有关。
    // 改变this指向。
    // 将数组入参变为一般入参
	var result = fn.apply(obj,args);
	//4. 对构造函数有返回值的处理判断
	return result instanceof Object ? result : obj;
}
console.log( create(Fun,18,'张三')   )

16.面试题:闭包

1. 闭包是什么

闭包是一个函数加上到创建函数的作用域的连接,闭包“关闭”了函数的自由变量。

2. 闭包可以解决什么问题【闭包的优点】

  • 2.1 内部函数可以访问到外部函数的局部变量
  • 2.2 闭包可以解决的问题
var lis = document.getElementsByTagName('li');
for(var i=0;i<lis.length;i++){
	(function(i){
		lis[i].onclick = function(){
			alert(i);
		}
		lis[i] = null;
	})(i)
}

3. 闭包的缺点

  • 3.1 变量会驻留在内存中,造成内存损耗问题。 解决:把闭包的函数设置为null
  • 3.2 内存泄漏【ie】 ==> 可说可不说,如果说一定要提到ie

17.面试题:原型链

1. 原型可以解决什么问题

对象共享属性和共享方法

2. 谁拥有原型

  • 函数拥有:prototype
  • 对象拥有:__ proto __
	function Fun() {
			//this.run = '1'
		}
		console.log(Fun.prototype);//constructor: ƒ}
		var obj = new Fun();
		console.log(obj.__proto__);//constructor: ƒ}
		console.log(Fun.prototype == obj.__proto__);//true

image.png

3. 对象查找属性或者方法的顺序

先在对象本身查找 --> 构造函数中查找 --> 对象的原型 --> 构造函数的原型中 --> 当前原型的原型中查找

image.png

4. 原型链

4.1 是什么?:就是把原型串联起来

4.2 原型链的最顶端是null

	<script type="text/javascript">

		function Fun() {
			// 2.到创建对象的构造函数中查找
			this.run = '1'
		}
		// console.log(Fun.prototype);//constructor: ƒ}
		//Fun.prototype.run = '2'
		var obj = new Fun();
		// console.log(obj.__proto__);//constructor: ƒ}
		// console.log(Fun.prototype == obj.__proto__);//true

		// 1.对象本身查找
		obj.run = '3';

		// 3.对象的原型中查找
		obj.__proto__.run = '4'

		// 4.当前原型的原型中查找
		Object.prototype.run = '5';
		console.log(obj.run);
		console.log("当前对象的原型:",Fun.prototype, obj.__proto__)
		console.log("当前对象的原型的原型:",obj.__proto__.__proto__);
	</script>

18.面试题: JS继承有哪些方式

  1. 方式一:ES6
//方式1
// 定义父类
class Parent{
	constructor(){
		this.age = 18;
	}
}
// 定义子类,继承父类
class Child extends Parent{
	constructor(){
		super();//super关键字可以在子类的构造方法中显示地调用父类的构造方法,
                //super()必须为子类构造函数中的第一行.
		this.name = '张三';
	}
}

let o1 = new Child();
console.log( o1,o1.name,o1.age );//Child {age: 18, name: '张三'}age: 18name: "张三"[[Prototype]]: Parent '张三' 18

2.方式二:原型链继承,实现共享。

// 方式2
function Parent(){
	this.age = 20;
}
function Child(){
	this.name = '张三'
}
Child.prototype = new Parent();//原型链继承
let o2 = new Child();
console.log( o2,o2.name,o2.age );//Child {name: '张三'} '张三' 20

3.方式三:借用构造函数继承,实现不了共享。

//方式3
function Parent(){
	this.age = 22;
}
function Child(){
	this.name = '张三'
	Parent.call(this);//让this指向,指向当前。
}
let o3 = new Child();
console.log( o3,o3.name,o3.age );//Child {name: '张三', age: 22} '张三' 22

4.方式四:组合式继承,即可以实现共享,又可以解决原型链继承的一些问题。

//方式4
function Parent(){
	this.age = 100;
}
function Child(){
	Parent.call(this);//让this指向,指向当前
	this.name = '张三'
}
Child.prototype = new Parent();//原型链继承
let o4 = new Child();
console.log( o4,o4.name,o4.age );//Child {age: 100, name: '张三'} '张三' 100

19.面试题:说一下call、apply、bind区别

1.共同点:功能一致
可以改变函数体内的this指向

语法: 函数.call()、函数.apply()、函数.bind()
2. 区别:
1. call、apply可以立即执行。bind不会立即执行,因为bind返回的是一个函数需要加入()执行。
2. 参数不同:apply第二个参数是数组。call和bind有多个参数需要挨个写。

fun.call()

var str = '你好';
var obj = {str:'这是obj对象内的str'}
function fun( name, age ){
	// this.name = name;
	// this.age = age;
	console.log("fun的this:", this , this.str );
}
//call立即执行,函数体fun内的this指向obj
fun.call( obj ); //fun的this: {str: '这是obj对象内的str'} 这是obj对象内的str

// fun.apply( obj ); //apply立即执行
//fun.bind( obj );  //bind不会立即执行,因为bind返回的是函数

// fun.call(obj,'张三',88); 
// fun.apply(obj,['张三',88]); //apply的第二参数是数组,才生效
//fun.bind(obj,'张三',88)(); //加括号才执行

fun();//Window {window: Window, self: Window, document: document, name: '', location: Location, …}
3. 场景:
  1. 用apply的情况
var arr1 = [1,2,4,5,7,3,321];
console.log(Math.max(arr1));//NaN
console.log( Math.max.apply(null,arr1) )//321
  1. 用bind的情况
var btn = document.getElementById('btn');
var h1s = document.getElementById('h1s');
btn.onclick = function(){
	console.log( this.id );//hls
}.bind(h1s)//.bind(hls),让this指向hls

20.面试题:sort背后原理是什么?

1.sort()方法

sort() 方法用于对数组的元素进行排序,并返回数组。默认排序顺序是根据字符串Unicode码点。

语法:array.sort(sortby);参数sortby可选。规定排序顺序。必须是函数。

注:如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。

// 方式一:
var arr1 = [12,11,1,23,'45','34',21,'b','a','ab','bc'];
console.log( arr1.sort() );//11) [1, 11, 12, 21, 23, '34', '45', 'a', 'ab', 'b', 'bc']

// 方式二:
var arr2 = [12,111,11,1,23,'45','34',21];
var arr3 = arr2.sort(function( a, b ){
	return a-b;//从小到大进行排序
})
console.log( arr3 );//[1, 11, 12, 21, 23, '34', '45', 111]


//  方式三:
var arr4 = [
	{name:'zhangsan',age:18},
	{name:'lisi',age:2},
	{name:'wangwu',age:50},
];

function compare(age){
	return function(a,b){
		var val1 = a[age];
		var val2 = b[age];
		return val1 - val2;
	}
}

var arr5 = arr4.sort(compare('age'));

console.log(  arr5  );
/*
0: {name: 'lisi', age: 2}
1: {name: 'zhangsan', age: 18}
2: {name: 'wangwu', age: 50}
*/

2.原理

V8 引擎 sort 函数只给出了两种排序 InsertionSort 和 QuickSort,数量小于10的数组使用 InsertionSort,比10大的数组则使用 QuickSort。

之前的版本是:插入排序和快排,现在是冒泡

原理实现链接:github.com/v8/v8/blob/…

710行

21.面试题:深拷贝和浅拷贝

共同点:复制

1. 浅拷贝:只复制引用,而未复制真正的值,所以一个改变,另一个也会改变。

image.png


//1. 浅拷贝,会互相影响
var arr1 = ['a','b','c','d'];
var arr2 = arr1;

arr1[0] = '你好吗';
arr2[1] = '还行';

/*['你好吗', '还行', 'c', 'd'] (4) ['你好吗', '还行', 'c', 'd']*/ 
console.log( arr1, arr2 );

var obj1 = {a:1,b:2}
var obj2 = Object.assign(obj1);//对象属性的合并
obj1.a = '100';
obj2.b = '你怎么养';
console.log( obj1,obj2);//{a: '100', b: '你怎么养'} {a: '100', b: '你怎么养'}

2.深拷贝:是复制真正的值,即复制真正独立的一份。

//2. 深拷贝:是复制真正的值,即复制真正独立的一份。(不同引用)
var obj3 = {
	a:1,
	b:2
}
// JSON.stringify 方法将某个对象转换成 JSON 字符串形式
// JSON.parse()【从一个字符串中解析出json对象】
var obj4 = JSON.parse(JSON.stringify( obj3 ));
obj3.a = '100';
obj4.b = '你怎么阳';
console.log( obj3, obj4 );//{a: '100', b: 2} {a: 1, b: '你怎么阳'}

递归形式

var obj5 = {
	a:1,
	b:2,
	arr:['a','b','c','d']
}

function copyObj( obj ){
	if(  Array.isArray(obj)  ){
		var newObj = [];
	}else{
		var newObj = {};
	}
	for( var key in obj ){
		if( typeof obj[key] == 'object' ){
			newObj[key] = copyObj(obj[key]);
		}else{
			newObj[key] = obj[key];
		}
	}
	return newObj;
}
console.log(  copyObj(obj5)  );//{a: 1, b: 2, arr: Array(4)}

辨明. for in 和 for of 的区别

  1. for...in 循环:只能获得对象的键名,不能获得键值
  2. for...of 循环:允许遍历获得键值
var arr = ['red''green''blue']
for (let item in arr) {
console.log('for in item', item)
}

/*
for in item 0
for in item 1
for in item 2
*/
for(let item of arr) {
console.log('for of item', item)
}
/*
for of item red
for of item green
for of item blue
*/

22.面试题:localstorage、sessionstorage、cookie的区别

一. 公共点:在客户端存放数据

二. 区别:

  1. 数据存放有效期
  • sessionStorage : 仅在当前浏览器窗口关闭之前有效。【关闭浏览器就没了】
  • localStorage : 始终有效,窗口或者浏览器关闭也一直保存,所以叫持久化存储。
  • cookie : 只在设置的cookie过期时间之前有效,即使窗口或者浏览器关闭也有效。
  1. localStorage、sessionStorage不可以设置过期时间。 cookie 有过期时间,可以设置过期(把时间调整到之前的时间,就过期了)
  2. 存储大小的限制
  • cookie存储量不能超过4k
  • localStorage、sessionStorage不能超过5M

根据不同的浏览器存储的大小是不同的。

sessionStorage.setItem('key','123');
localStorage.setItem('key','456');
created(){
let date=1000,
let time=1000*60*60*24;
time=date.getTime()+time;
document.cookie='name=789;expires='+date.toUTCString()+'';
}