2019面试笔记

32次阅读

共计 5501 个字符,预计需要花费 14 分钟才能阅读完成。

一.js 原始类型:
在 js 中,存在着 6 种原始值:
* boolean
* number
* string
* undefined
* null
* symbol
注意:虽然 typeof null 输出的是 object,但是 null 并不是 object 类型,因为早期的 bug;
number 类型是浮点数类型,所以才会有 js 精度问题出现。0.1+0.2 !== 0.3。原始类型存储的都是值,是没有函数可以调用的。
symbol 类型:symbol 类型的对象永远不相等,即便创建他们的时候传入了相同的值,可以借助此特性解决属性名的冲突问题。for…in.. 拿不到 symbol 类型的属性值,而且要用 [] 的方式来读取 symbol 类型的属性值,用点的方式读不到。
二. 对象(Object)类型:
在 js 中,除了原始类型其他的就是对象类型了。Q1:对象类型和原始类型的不同之处?
答:对象类型和原始类型不同的是,原始类型存贮的是值,对象那个类型存储的是地址(指针)。当创建了一个对象类型的时候,计算机回在内存
中开辟一个空间赖存放值,但是我们需要找到这个空间,这个控件会拥有一个地址(指针)。当我们将变量赋值给另外一个变量时,复制的是原本变量的地址(指针)。
Q2:函数参数是对象会发生什么?
答:函数参数是对象的话,在函数内部可能会改变这个参数对象,生成新对象。
三.typeof VS instanceof
Q1:typeof 是否能正确判断类型?
答:对于原始类型来说,typeof 除了 null 都可以正确显示类型,typeof null 显示的是 object 是错误的。对于其他的,除了 function 会返回 function,其他都会返回 object。
Q2. instanceof 能正确判断对象的原理是什么?
答:instanceof 内部机制是通过原型链来判断对象类型的。
eg: const Person = function () {}
const p1 = new Person() p1
instanceof Person // true
var str = ‘hello world’ str instanceof String // false
四. 类型转换:
1. 转 Boolean: 在条件判断时,除了 undefined,null,0,-0,NaN,”,false,其他所有值都转化为 true,包括所有对象。2. 对象转原始类型:对象在转换类型的时候,会调用内置的 [[ToPrimitive]] 函数,对于该函数来说,
算法逻辑一般来说如下:
* 如果已经是原始类型了,就不需要换砖了
* 调用 x.valueof(), 如果转换为基础类型,就返回转换的值
* 调用 x.toString(), 如果转化为基础类型,就返回转换的值
* 如果都没有返回原始类型,就会报错 1)四则运算符:加法运算符特点:
* 运算中如果一方为字符串,那么就会把另一方叶转换为字符串,1 + ‘1’ // ’11’
* 如果一方不是字符串或者数字,那么会将它转化为数字或者字符串:true+ true // 2 ; 4 + [1,2,3] // ‘41,2,3’
注意:+ ‘a’ 是快速将 ’a’ 转化成 number 类型的写法;’a’ + + ‘b’ // ‘aNaN’ 那么对于除了加法运算符其他运算符来说,只要一方是数字,那么另一方就会被转化为数字。反正最终都是要转化为数字来运算。
4 * ‘3’ // 12 4 * [] // 0 4 * [1,2] // NaN 2)比较运算符 大于小于运算都会转化为数字进行运算。
关于 ==:
*undefined 等于 null
* 字符串和数字比较时,字符串转数字
* 数字和布尔类型比较时,布尔转数字
* 字符串和布尔比较时,两者转数字
五.this
this 永远指向最后调用它的那个对象。1. 改变 this 指向:
* 使用 es6 箭头函数
* 在函数内部使用 _this = this
* 使用 apply,call,bind
* new 实例化一个对象
2. 箭头函数:箭头函数的 this 始终指向函数定义时的 this,而非执行时(意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this)。
箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值。
3.apply,call,bind 区别 apply 和 call 只是传入的参数不同。call 接受的是若干个参数列表,而 apply 接受的是一个包含多个参数的数组。bind 和 apply,call 区别:
bind 创建了一个新的函数,需要手动去调用它;
六.new 的过程:

创建一个空对象 obj;
将新创建的空对象的隐式原型指向其构造函数的显示原型;
使用 call 改变 this 的指向;
如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值时一个新对象的华那么直接返回该对象。

七. == VS ===
Q1: == 和 === 有什么区别?
答:对于 == 来说,如果双方的类型不一样的华,就会进行类型转换。
流程:
* 首先判断两者类型是否相同
* 判断是否为 undefined == null //true
* string 转 number 比较 * boolean 转 number 比较
* object 转原始类型比较 但是,=== 的比较只需要判断两者类型和值是否相同。
八. 闭包:
Q1: 什么是闭包?
答:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。
Q2:经典面试题:循环中使用闭包解决 var 定义函数的问题:答:第一种:使用闭包的方式:for (var i = 1; i <=5 ; i++) {;(function(j) {setTimeout(function timer() {console.log(j) }, j * 1000) })(i)}
第二种:用 let 定义的方式:for (let i=1 ; i <=5 ; i++) {setTimeout(function timer() {console.log(i) }, i * 1000) } Q3:闭包的优缺点?闭包作用(优点):可以读取到函数内部的变量,可以让这些变量的值始终保持在内存中,不会被垃圾回收掉。
闭包缺点:因为闭包会使得函数中变量保存在内存中,所以会增大内存使用量,使用不当很容易造成内存泄漏。浪费内存。清除闭包:退出函数之前,将不使用的局部变量删除。
九. 深浅拷贝:
1. 堆和栈:栈(stack)为自动分配的内存空间,它由系统自动释放;而堆(heap)则是动态分配的内存,大小不定也不会自动释放。2. 基本数据类型存放在栈中,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。基本类型的比较是值的比较。3. 引用类型存放在堆中,变量实际上存放的是栈 1 内存的指针,引用类型的值可变。引用类型的比较是引用的比较。所以比较两个引用类型,是看其的引用是否指向同一个对象。Q1. 浅拷贝?
答:浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。
Q2. 深拷贝?
答:深拷贝递归复制了对象的所有层级属性。
Q3. 深拷贝,浅拷贝以及赋值的区别?
答:浅拷贝,深拷贝和原数据都不是指向同一对象,而赋值对象和原对象是指向同一对象。
第一层数据为基本类型,改变会使原数据改变,深拷贝以及浅拷贝不会。原数据中包含子对象,改变子对象会使赋值对象以及浅拷贝对象改变,不会使深拷贝对象改变。
Q4. 如何实现浅拷贝?
答:1. 可以通过 Object.assign 来解决。(Object.assign()方法用于将所有可枚举的值从一个或多个源对象复制到目标对象。它将返回目标对象。)
eg:
let a={age: 1}
let b=O bject.assign({}, a)
a.age=2 console.log(b.age) // 1
2. 可以通过展开运算符 … 来实现浅拷贝
eg:
let a={age: 1}
let b={…a}
a.age=2 console.log(b.age) // 1
Q5. 如何实现深拷贝?
答:1. 该问题可以通过 JSON.parse(JSON.stringify(object))来解决。
eg: let a={age: 1, jobs: { first: ‘FE’} }
let b=J SON.parse(JSON.stringify(a))
a.jobs.first=’native’
console.log(b.jobs.first) // FE
但是该方法也是有局限性的:
* 会忽略 undefined
* 会忽略 symbol
* 不能序列化函数
* 不能解决循环引用的对象
十:原型和原型链:
js 是基于原型的继承。1. 使用构造函数创建对象后,新对象与构造函数没有关系了,新对象的 [[prototype]] 属性指向的是构造函数的原型对象。2. 构造函数、原型和实例的关系:
* 构造函数都有一个属性 prototype,这个属性是一个对象(Object 的实例)
* 原型对象里面有个 constractor 属性,该属性指向原型对象所属的构造函数。当原型对象被修改过,constractor 就不一定指向原来的构造函数了。
* 实例对象都有一个_proto_属性,该属性也指向构造函数的原型对象,它是一个非标准属性,不可以用于编程,它用于浏览器自己使用。
3.prototype 和_proto_的关系:
* prototype 是构造函数的属性;
* _proto_是实例对象的属性;
* 这两者都会指向同一个对象。
总结:* 函数也是对象,对象不一定是函数;
* 对象的本质:无序的键值对集合,键值对当中的值可以是任意数据类型的值;
* 对象就是一个容器,这个容器当中放的是属性和方法。
4. 原型链:
原型链就是可以理解为有限的实例对象和原型之间组成的有限链,就是用来实现属性共享和继承的。
5. 属性搜索:
* 在访问对象的某个成员的时候在对象中查找是否存在;
* 如果当前对象中没有就在构造函数的原型对象中查找;
* 如果原型对象中没有就在原型对象的原型上找;
* 直到找到 Object 的原型对象的原型是 null 为止。
eg:
var arr = []
原型链:arr -> Array.prototype -> Object.prototype -> null
Q1:什么是原型?
答:原型也是一个对象,并且这个对象中包含了很多函数。原型的 constract 属性指向构造函数,构造函数又通过 prototype 属性指回原型,到那时并不是所有函数都具有这个属性。
十一:js 继承:
1. 原型链继承:将父类的实例作为子类的原型。
eg:Cat.prototype = new Animal()
缺点:子类无法给父类传参,子类要新增属性和方法的话,必须要在 new Parent()这样的语句之后执行,否则会被覆盖。
2. 构造继承:使用父类的构造函数来增强子类的实例,等于是在子类的构造函数内部执行 Parent.call(this)。
eg:function Cat(name) {
Animal.call(this);
this.name = name || ‘Tom’;
}
var cat = new Cat();
缺点:没有原型,每次创建一个子类实例都要执行一遍 Parent 函数。
3. 组合式继承:结合原型链继承和构造继承,组合式继承在使用过程中会被调用两次:一次是创建子类的时候,一次是在子类构造函数的内部。
eg: function Cat(name){
Animal.call(this);
this.name = name || ‘Tom’;
}
Cat.prototype = new Animal();
3.class 继承:
class 继承的核心在于使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this,value).
eg:
class Parent {
constructor(value) {
this.val = value
}
getValue() {
console.log(this.val)
}
}
class Child extends Parent {
constructor(value) {
super(value)
this.val = value
}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

十二:var、let 及 const 区别:
1.var 和 let 区别:
* let 有块级作用域的说法,也就是 let 声明的变量只能在这个块内部使用,别的地方不能使用。var 声明的变量可以在全局使用,var 在函数内部声明的变量只能在函数内部使用。
* let 没有变量提升,var 有变量提升
* let 声明的变量存在“暂时性死区”,而 var 声明的变量没有
* let 不允许在同一个快内重复声明一个变量。因此也不能在函数内部重复声明一个变量。
* var 声明的变量会挂载到 window 上,而 let 声明的对象不会挂载到 window 上。

const 特性:

const 声明一个只读的常量。一旦声明,常量的值就不能改变;
const 声明的变量必须初始化,不然会报错;
const 声明的变量也存在暂时性死区;
const 声明的变量只在所声明的块级作用域内有效;
const 声明的常量也与 let 一样不可重复声明。

Q1. 什么是提升?
答:就是变量可以在神明之前使用,并且值为 undefined。
Q2. 什么是暂时性死区?
答:在 let 命令声明之前,该变量都素 hi 不可用的。
十三. 模块化:
Q1. 为什么要使用模块化?
答:使用模块化可以带来以下好处:
* 解决命名冲突;
* 提供复用性;
* 提高代码可维护性。
Q2. 实现模块化的方式?
CommonJS AMD CMD
1.require/exports:
遵循 CommonJS/AMD,只能在运行时确定模块的依赖关系及输入 / 输出的变量,无法进行静态优化。
2.import/export:
遵循 ES6 规范,支持编译时静态分析,便于 JS 引入宏和类型检验,动态绑定。

正文完
 0