前端开发JS面试题笔记

@[TOC]类型和变量计算
知识点:数据类型分为值类型和引用类型,值类型如:
let a=100;
let b=a;
a=200;
经过上面的程序后,b的值是 100;a的值是200;因为a重新赋值了,它们都是基本的值类型,在用=号复制的时候,是直接赋值的。(值类型有undefined,string,number,boolean,symbol)***
引用类型 let a={name:“张三”} let b=a;那么a和b都是指向同一个内存地址。这个内存地址存储了一个对象。这样是一个引用类型
引用类型包括 对象,数组 函数和null也是比较特殊的引用类型,null特殊在于它指向空地址.
*

tepyof运算符
typeof能识别所有的值类型(undefined,number,string,boolean,symbol);能识别出函数function,能判断是否是引用类型(引用类型有:数组,对象,null和函数是特殊引用类型)

let a; 
typeof(a);//的结果为undefined;`
const str="abc";
typeof(str);//的结果为string
const n=100;
typeof(n);//的结果为Number
const b=true;
typeof(b);//的结果为boolean
const s=Symbol("s");
typeof(s);//的结果为Symbol
function fun(){};
typeof(fun);//的结果为function

typeof对于引用类型,只能检测识别出是否是引用类型。即 检测结果为 Object;如果要判断arr是否是数组,需要使用arr instanceof Array 进行判断(结果为true或者false);判断obj是否是对象,使用obj instanceof Object(结果为true或者false)

const arr=[1,2,3,4];
typeof(arr);//的结果为Object
const obj={name:"张三"};
typeof(obj);//的结果为object
typeof(null);的结果为object

深拷贝
深拷贝用于拷贝一个对象或者数组等引用类型;比如有如下对象:

const obj={
	age:20,
	name:"张三",
	address:{
		city:"北京"
	},
	arr:[1,2,3,4]
}
const ojb1=obj;
ojb1.address.city="上海";

在上面这段代码中,因为obj是引用类型;所以obj本身存储的不是对象内容,它是存储了这个对象在内存堆中的地址,obj指向这个对象,const obj1=obj; 也是让obj1指向了obj所指向的地址。那么obj1.address.city被修改了,就是修改了obj1和obj共同指向的地址中的那个内容。自然,obj.address.city 的打印结果也是上海。

console.log(obj.address.city);//运行结果为上海。

而如果要实现 修改了obj1的地址后,obj地址还是北京 ,就不能直接const obj1=obj;而需要使用到深拷贝。
实现深拷贝,我们需要写一个函数,名字就叫做deepClone吧。这个函数需要传一个参数,要拷贝谁,就传谁。最终它要返回一个东西, 返回一个对象所以,第1步的写法:

function deepClone(obj={}){

return {}
}

第2步的思路:首先传入的obj应该要是个对象或者数组吧,只能对象或者数组才要做深拷贝,那么我们可以用判断语句来判断一下:
如果obj即不是对象也不是数组,或者等于null,就没有必要做深拷贝,直接返回(还记得前面的知识吗,typeof arr和typeof obj 都是输出为object,所以这里没有说typeof obj会不等于Array。因为typeof的检测结果没有Array,要判断是否是数组要用 arr instanceof Array)

function deepClone(obj={}){
if(typeof obj !=="object" ||obj==null){
		return obj
	}
return {}
}

第3步的思路:先初始化一个返回结果result.然后分开来操作,如果是数组返回数组的格式,如果是对象返回对象的格式,这里的判断用到了arr instanceof Array的语法。返回的要做处理,无论对象还是数组,都可以使用for in进行遍历,所以需要用到for(let key in obj)进行遍历

```javascript
function deepClone(obj={}){
	if(typeof obj !=="object" || obj==null){
				return obj
		}
let result;
if(obj instanceof Array){
		result=[]
	}
else{
	result={}
}
for(let key in obj){

}
return {}
}

补充:下午6点写的笔记,晚上在vscode上重新写一遍的时候出现了些问题:主要在于hasOwnProperty这个的写法,一定要注意Pro 大写,不能写成pro; 另外出有个问题是 for(let key in obj) 这个遍历,重新写的时候 忘了写let。共勉~

第4步思路:遍历对象的key,然后在里面判断key是不是obj自身的属性(用hasOwnproperty),即 obj.hasOwnproperty(key),如果是, 用一个递归写法,所以for in里面的写法是:

for(let key in obj){
	if(obj.hasOwnproperty(key)){
		result[key]=deepClone(obj[key])
	}
}

递归的过程是:它们在拷贝age的时候,会符合第1条,所以会直接返回,拷贝第2条name 的时候,符合第1条if语句,也是直接返回,拷贝address的时候,符合else,返回一个对象 。这样最终才能真正的深拷贝过来。
到此就完成了这个深拷贝,完整的写下下来就是:

function deepClone(obj={}){
	if(typeof obj !=="Object" || typeof obj==null){
			return obj
		}
	let result;
	if(obj instanceof Array){
			result=[]
		}
	else{
			result={}
		}
	for(let key in obj){
			if(obj.hasOwnproperty.(key)){
				result[key]=deepClone(obj[key])
			}
		}
return {};
}

使用的时候:

const obj2=deepClone(obj)

即可实现调用deepClone函数来进行深拷贝。

(考试题一)、typeof能判断哪些类型?
typeof能判断 undefined,number,string,boolean,Symbol ,这些值类型,还能判断 引用类型 像对象 数组,包括特殊的类型 null和function。

(考试题二)、何时使用==,何时使用===
除了null,其他时候用=

(考试题三)、值类型和引用类型的区别
用=号赋值的时候,值类型是直接复制那个值;引用类型是指向地址。

(考试题四)、手写深拷贝(也叫深克隆函数)

function deepClone(){
if(typeof obj !=="object" || obj==null){
	return obj;
}
let result;
if(obj instanceof Array){
	result =[]
	}
else{
	result={}	
}
for(let key in obj){
	if(obj.hasOwnProperty(key)){
	reslut[key]=deepClone(obj[key])
	}
	
	}
return {}
}

@[TOC] 变量计算-类型转换
变量计算的时候要特别注意到它的类型转换,经常用得多的地方是字符串拼接,==判断,if语句和逻辑运算。比如下面的一些案例:

const a=100+10;//结果是数字(number)110
const a=100+"10";    //结果是字符串(string)10010 
const c=true+"10";    //结果是字符串(string)    true10

只要是 + 字符串,结果就是字符串。类型转换有一些词,可以把字符串转换成数字,比如:

const a=100+parseInt("10");   //结果是110,

再要注意的是 == 判断;== 的时候会尝试进行类型转换后再判断相等与否,比如:

100=="100"      //结果是true,因为它们会尝试进行类型转换后再相等
0==""           //结果也是true
0==false        //结果也是true
false==""       //结果也是true
null==undefined  //结果也是true

而=== 就会以它们各自当前的类型 值去进行比较。值和类型 任何一样都不同 ,那么结果都是false.实际工作中

@[TOC]原型和原型链
js本身是基于原型继承的语言。class只是形式上的写法,本质是基于原型的继承。
题目:如何判断一个变量是不是数组?之前已经学过了,arr instanceof Array ;结果为true 就是数组。但是这里判断不是重点,这是如何判断的才是本节的知识点;
题目:手写一个简易的jQuery,考虑插件和扩展性。这个没有学。所以这题先放一边。
题目:class的原型本质,怎么理解?涉及到class和继承,原型和原型链。
知识点:
class本身是面向对象的语法实现,可以通过constructor去构建一些东西,比如 属性 ,方法。(比如构建一个 Student,里面有 name age 等属性, 还有打招呼的 方法)

class

class Student{
    constructor(name,number){
        this.name=name;
        this.number=number;//this表示当前正在构建的实例
    }
    sayHi(){
        console.log(
            `姓名:${this.name},学号:${this.number}`
        );
    }
}

可以通过类声明一个对象/实例,传入姓名和学号

const xialuo=new Student("夏洛",100);
console.log(xialuo.name);
console.log(xialuo.number);
xialuo.saiHi();

继承
什么叫做继承呢?当我们有很多个class,这里面有一些公共的属性,就可以把它单独提出来,比如上面声明的一个Student的class,还可以声明一个Teacher的class,它们有一些能共用的属性,比如打招呼,比如 他们都有姓名。 等 。 就可以单独声明一 个People的class,里面有 name属性, 有打招呼的方法。Student和Teacher 都可以通过extends关键字来继承People 的这些共用属性的方法。通过super来执行父类的函数和构建过程。

// 父类
class People{
    constructor(name){
        this.name=name;
    }
    eat(){
        console.log(
            `${this.name}在吃饭`
        );
    }
}

// 子类
class Student extends People{
    constructor(name,number){
        super(name);  // super的意思是把名字这个属性给到父类去处理
        this.number=number;//number必须它自己处理,因为没有可继承的
    }
    saiHi(){
        console.log(
            `${this.name},学号${this.number}`
        );
    }
}

// 老师类
class Teacher extends People{
    constructor(name,zhuangye){
        super(name);
        this.zhuangye=zhuangye;
    }
    saiHi(){
        `我是${this.name},我教的专业是${this.zhuangye}`
    }
}

类型判断 instanceof
可以通过instanceof去判断这个变量属于哪个class,属于哪个构造函数。

xialuo instanceof Student;  //true
xialuo instanceof People;   //true
xialuo instanceof Object;   //true

还可以判断数组和对象

 [1,2] instanceof Array;   //true
 [1,2] instanceof Object;  //true
{} instanceof Object;   //true

问题是 为什么可以通过intanceof 来判断?xialuo instanceof Student 为true可以理解,因为xialuo是student的一个实例,People也参与了构建xialuo; 但是xialuo instanceof Object 为true是为什么? 因为Ojbect 是所有class的父类。People是Student的父类,而Ojbect是People的父类。这个不用我们自己去写继承,是JS本身就会这么做。所有的引用类型的instanceof Ojbect 结果都是true;Object是所有引用类型的父类

class Student extends People{ } 那么Student 不直接继承Object;它是继承People,然后People继承Object;如果没有写extends这个继承关键字,就是直接继承Object;

我们来通过原型来深入理解一下instanceof。首先class 它其实是一个函数,通过typeof Stucent 可以判断出来。
在这里插入图片描述
为什么会这样呢?因为class它只是语法上的形式,它符合JS的原型继承。本质上还是一个function,通过function来实现的,但它的写法是class的写法。

在上面的xialuo的实例中,它有xialuo.number ; xialuo.saiHi()等方法属性,不过它还有一个

 `xialuo.__proto__`

这个我们叫做xialuo 的隐式原型,它里面也有saiHi()方法。

xialuo.__proto__.saiHi()

而且这个实例隐式原型和class Student的显式原型全等===;

xialuo.__proto__.saiHi()===Student.prototype.saiHi()

规则:

每个class都有显式原型 protopype
每个实例都有隐式原型__proto__
 
执行规则:
获取属性xialuo.name或者执行方法xialuo.saiHi()时,先在自身属性上和方法上寻找,如果找不到则自动去隐式原型上查找,

在这里插入图片描述

class People{};
class Student extends People{};
const xialuo=new Student();
那么,

xialuo.__proto__=Student.prototype;  //true;
People.prototype=Student.prototype.__proto__

在上面的图片原型链中,xialuo有它自己的属性name 和 number; 怎么判断验证这是它自己的属性呢?

xialuo.hasOwnProperty("name")   //结果为true,表示name是它自己的属性。
xialuo.hasOwnProperty("saiHi")  //结果为false,saiHi方法不是xialuo自己的。

在这里插入图片描述
People 其实是继承于Object。Object 的隐式原型`object.proto 指向null。

再来看instanceof, 下面这三行代码,结果都为true,它的原理就是顺着原型链能否找到右边的。能找到则为true;

xialuo instanceof Student;
xialuo instanceof People;
xialuo isntanceof Object;

再来解答一下开始的面试题
如何准确判断一个变量是不是数组?
答:a instanceof Array; 如果为true则是数组。(instanceof的原理就是顺着原型链找上去)

手写一个简易的jQuery
Jquery现在用得不多。
class原型本质怎么理解?
解答思路:(最主要的是原型和原型链的图示),属性和方法的执行规则。
答:在class中 ,执行的时候 ,会先在自身的属性和方法上找,找不到会顺着原型去它的上一级的原型上去找。所以实例xialuo可以使用继承过来的Student中的saiHi()方法和属性。

@[toc]作用域和闭包
题目:
this在不同应用场景下如何取值?
手写bind函数(bind函数是改变this指向的方法之一)
实际开发中闭包的应用场景,举例子说明

知识点:
局部作用域和全局作用域,以及自由变量

 let a=0;
        function fn1(){
            let a1=100;
            function fn2(){
                let a2=200;
                function fn3(){
                    let a3=300;
                    return a+a1+a2+a3
                }
            }
        }

在上面这个函数中,a 是全局作用域,a1 只能在fn1这个函数范围内使用,它是局部作用域,a2在fn2中使用;a3在fn3中使用,它们都是局部作用域。

而反过来出来,fn3中 没有a,a1,a2, 在fn3中需要使用这三个变量,都需要向外面寻找。所以a a1 a2是自由变量。

闭包
闭包分两种情况:
函数作为返回值的情况:

function create(){
    let a=100;
    return function(){
        console.log(a);
    }
}
let fn=create();
let a=200;
fn(a)//结果:100

分析:fn(a) 函数执行 在全局作用域,没有被谁包裹,let a=200也是全局作用域。 函数定义是在create()函数这个作用域内,a=100也是在create()作用域内,函数里面去执行函数打印a 的时候,a是一个自由变量(因为return function()里面没有定义a),所以它会往上一级作用域寻找,(上级作用域指的是函数定义地方的上级作用域)就会找到create()这个作用域里面找,结果找到了 let a=100;所以打印a=100;

函数作为参数被传递

function print(fn){
    let a=200;
    fn()
}
let a=100;
function fn(){
    console.log(a);
}
print(fn)  //结果:100

这题也是同理,fn()函数是在全全局作用域里面定义的,所以寻找a也是在全局作用域里面寻找。

小结:所有的自由变量的查找,都是在函数定义的地方开始,向上级作用域查找,不是在函数执行的地方。

this
一、作为普通函数去调用(this指向window)

	function fun(){
	console.log(this)   
}
fun()   //运行结果是window,this指向window

二、使用call apply,bind去调用(这时this指向传入的参数)

function fun1(){
console.log(this)
}
fun1()   //直接运行的话是指向window

使用call  bind apply执行的话
fun1.call({x:100})  //输出是{x:100};  call里面传入的是什么, 就指向什么。 

如果是bind方法的话就会,它必须返回一个新的函数,需要在新的函数上执行。然后this指向传入的参数
const fun2=fun1.bind({x:200});
fun2();   //运行结果是  {x:200} 

三、作为对象方法调用,(this指向对象)

const zhangsan={
		name:"张三",
		sayHi(){
			console.log(this)
//这里的this 作为zhangsan这个对象的sayHi()方法被执行,那么this指向的是zhangsan 这个对象,即它作为对象的方法执行的时候 返回的就是对象本身。
		},
		wait(){
			setTimeout(function(){
					console.log(this)

//这里的this指向就不同了,它虽然也是在zhangsan这个对象的wait()方法里面写的,但是它的执行逻辑是:setTimeout()里面有个function函数,这里的runction函数被执行 和作为普通方法调用情况是一样的,zhangsan.wait()只是触发的条件,所以这里的this 指向是window。
						})
		}
	}

上面的案例再改一下:
const zhangsan={
	name:"张三",
	sayHi(){
		console.log(this)  //指向当前对象没有毛病
	},
	waitAgain(){
		setTimeout(()=>{
			console.log(this)
//此时这里的this就指向当前对象本身了。为什么呢,如果在waitAgain()里面直接打印this,那肯定和上面的sayHi()里面的this是一样的(对象的方法调用里,this指向对象),这里的setTimeout()里面有个箭头函数,箭头函数触发的不是xhangsan这个对象的方法,但是箭头函数 它的this 永远是取它上级作用域的this。
			})
			}
	
}

四、在class的方法中调用(this指向calss的实例)
class里面的方法和属性中的this指向的是实例对象。

五、箭头函数调用(this指向它上一层的this指向)
上面有说过了,

this取什么值是在函数执行的时候确定的,不是在定义的时候确定
再来看下题目:
this在不同场景下取什么值?

答:作为普通函数被调用的时候 this值是window,在class中被调用的时候 指向的是class的实例;作为对象方法被调用的时候 指向的是对象本身;箭头函数中指向的是上一层的this; 使用bind call apply 调用的时候 指向的是传入的参数;

手写bind函数
答题:
function fun1={
console.log(this)
}
const fun2=fun1.bind({x:100});
fun2(); // this指向{x:100}

	答题2:

function fun1(a,b){
console.log(this,“this”);
console.log(a,b);
return “this is fun1”
}
const fun2=fun1.bind({x:100},10,20) //bind会把{x:100} 传给this
//bind可以传入多个能数,但是只有第一个才传给this 。
console.log(fun2());

*这里插入一个题外的知识,前面讲过原型链,fun1.bind 这个方法 ,bind是不是fun1自身的方法呢?可以通过
fun1.hasOwnProperty(“bind”) ;运行看看,结果是false;可见不是,那 bind 是哪里来的呢?fun1是一个函数,函数可以
理解为它是一个实例,它有隐式原型 "fun1.__proto__ ",它的隐式原型 等于 Function.prototype;

fun1.__proto__ === Function.prototype;   //true

Function.prototype里面有bind,call,apply 这些API;所以它们可以顺着原型链找到调用它。
*

实际开发中闭包的应用场景举例说明?
先回顾下 闭包,闭包是作用域应用的一种特殊情况,主要表示是函数定义和函数返回的地方不一样,一种是函数作为参数被传递, 一种是函数作为返回值被返回。
答题:闭包在开发中的应用,主要是隐藏数据,不让外部去改变数据。

       // 闭包隐藏数据,只提供API的案例

        function createCache(){
            const data={}  //闭包中的数据被隐藏,不被外界访问

            return{
                set:function(key,val){
                    data[key]=val
                },
                get:function(key){
                    return data[key]
                }
            }
        }
        const c=createCache();
         c.set("a",100);  
         console.log(c.get("a"));
// 这里是怎么隐藏数据的?data是在cache函数的作用域里面,外界无法访问到;
// return{set和set}就是向外暴露的api,这两API的功能是设置和读取data里面的{key,val}

创建10个a标签,点击对应标签弹出对应的序号
答:

// let a,i;    写在这里的话,a,i就是全局变量,那点击就会全部变成10
        for(let i=0;i<10;i++){
            let a=document.createElement("a");
            a.innerHTML=i+"<br>";
            a.addEventListener("click",function(e){
                e.preventDefault();
                alert(i)
            })
            document.body.appendChild(a)
        }

// let a, i写全局变量的话,像这样写的话,每个弹出来的都是10;我们要求的是0弹出是0,1弹出1,2弹出2
// 为什么会弹出10呢,

    过程:几毫秒就创建好了这个程序,遍历了10遍,每个都绑定了一个点击事件;
     但是这个事件没有执行,事件什么时候执行呢?什么时候click什么时候执行;
     所以当每个点击被click的时候,这个i早就变成了10了。因为i的作用域是全局作用域;

     解决方法就是 把i定义到for里面,为什么放这就可以了呢,因为let i放这里,它是定义
     在for这个块作用域里面,每次 for循环遍历执行的时候,都会形成一个新的块,每个块的
     i值都不一样。从0到9,共有10个块,而click的时候,i的值就会在块作用域里面去寻找。

@[TOC]异步和单线程
题目:同步和异步的区别是什么?
答:异步不会阻塞代码往下执行,同步会阻塞代码执行;
手写Promise加载一张图片(考点 Promise )
前端使用异步的场景有哪些?
下面图片那个题目 的打印顺序是什么?13542在这里插入图片描述
知识点:单线程和异步是什么,异步的应用场景是什么,callback hell(回调地狱)和Promise(就是为了解决回调地狱的),
单线程的背景:
JS是单线程语言,只能同时做一件事。
JS和DOM渲染共同一个线程,所以在DOM渲染的时候JS不能执行,JS执行时候DOM渲染也必须停止;

异步是基于单线程的背景产生的,遇到等待时(网络请求 定时任务等)不能卡住,所以需要异步, 异步是基于callback函数形式来调用的,什么叫call函数形式呢,后面再说,先来看一个异步案例:

console.log(100);
setTimeout(function(){
		console.log(200)
		},1000)
console.log(300)
//运行结果是:  100  300  200

在这里,先打印出100,第二个是 1秒后打印200 ,这时候 不能卡在这里吧?所以 会继续往下执行打印300,然后时间到了1秒后会打印出200;
function () {
console.log(200)
}
这里的function就叫做callbakc回调函数;延时器的意思是1000毫秒后执行回调函数 打印200; 异步的特点是不会阻塞程序继续往下执行;

同步就是按顺序一个一个执行下去。执行完前面的才会执行后面的。像下面的案例中alert()的弹窗,如果我不点击确定 console.log(300)会一直等待,不执行。

console.log(100);
alert(200);
console.log(300)

异步的应用场景
前端开发中,用到异步的主要场景 主要是网络请求,Ajax 图片加载;定时任务,如setTimeout,setInterval。

Promise的基本使用
先来看个回调地狱的例子,如下面的,根据第1分数据获取第2分数据,根据第2份数据获取第3份数据,如此下去,越陷越深。
在这里插入图片描述

JS基础知识总结:
一、变量类型和计算
问题一:typeof能判断哪些类型
问题二:何时使用=何时用
问题三:值类型和引用类型的区别
问题四:手写深拷贝
知识点:
值类型 和引用类型,堆栈模型,深拷贝,typeof运算符,类型转换,truly,falsely变量
二、原型和原型链
问题一:如何准确判断一个变量是不是数组
问题二:写一个简易的jQuery
问题三:calss的原型本质怎么理解
知识点:calss和继承,intanceof,原型链和原型的图示和执行规则
三、作用域和闭包
问题一:this的不同应用场景和如何聚会
问题二、手写bind函数
问题三:实际开发中闭包的应用场景,举例说明
问题四:创建10个标签点击弹出对应序号
知识点:作用域和自由变量,闭包的两种常见形式和自由变量的查找规则,this指向问题
四、异步和单线程
问题一、同步和异步的区别
问题二、手写Promise加载一张图片
问题三、前端使用异步的场景有哪些
知识点:异步和单线程,它们的区别、应用场景:网络请求和定时任务,Promise解决回调地狱的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值