1.谈谈对原型链的理解。
1)原型对象:每一个函数在创建时都会被赋予一个prototype属性,它指向函数的原型对象,这个对象可以包含所有实例共享的属性和函数。
2)原型链:对象的每个实例都具有一个___proto__属性,指向的是构造函数的原型对象,而原型对象同样存在一个_proto__属性指向上一级构造函数的原型对象,就这样层层往上,直到最上层某个原型对象为null。
3)是null而不是object.prototype
原型链上的所有节点都是对象,不能是字符串、数字、布尔值等原始类型。规范要求原型链必须是有限长度的。你没法访问null的属性,所以起到了终止原型链的作用;另一方面,null在某种意义上也是一种对象,即空对象,因为null一开始就是为表示一个“空”的对象存在的
2. js有哪些数据类型?
1)基本数据类型 Number String Boolean Null undefined
2)引用数据类型 Array object
3. js有哪些判断类型的方法?
1)typeof:用于一般变量返回的类型都是字符串形式,
2)instanceof:后面一定要是对象类型
3)Object.prototype.toString.call():适用于所有了类型
4.ES5如何实现继承?
原型链继承/构造继承/复制继承/组合继承/寄生组合继承
4.1原型链继承
son.prototype=new Father()
son.constructor=son
优点:
1.简单,容易实现
2继承关系纯粹:生成的实例即是子类的实例,也是父类的实例。
3.可以通过子类直接访问父类原型链属性和函数
缺点:
1.子类的所有实例共享父类的属性,如果父类中有引用类型,某个子类改变值会引起其他子类中的值发生变化。
2.在创建子类实现时,无法向父类的构造函数传递参数
3.无法实现多继承
4.2构造继承
是在子类的构造函数中通过call函数改变this的指向,调用父类的构造函数,从而能将父类的实例的属性和函数绑定到子类的this上.
function Son(name){
Animal.call(this);
this.name=name
}
优点:
1.可解决子类实例的共享父类属性的问题:call函数实际是改变了父类构造函数中this的指向,
2.创建子类的实例时,可以向父类传递参数
3.可以实现多继承
缺点:
1.实例只是子类的实例,并不是父类的实例。
2只能继承父类实例的属性和函数。并不能继承原型对象上的属性和函数。
3.无法复用父类的实例函数,子类生成的每个实例都会拥有父类实例函数的引用,这回造成不必要的内存消耗。影响性能。
4.3复制继承
首先生成父类的实例,然后通过for ..in遍历父类实例的属性和函数,并将其依次设置为子类实例的属性和函数或者原型对象上的属性和函数。
优点:
1.支持多继承
2.能同时继承父类实例的属性和函数与原型对象上的属性和函数
3.可以向父类构造函数中传递值
缺点:
1.父类的所有属性都需要复制,消耗内存
2.实例只是子类的实例,不是父类的实例
4.4组合继承
组合了构造继承和原型继承两种方法,一方面在子类的构造函数中通过call)函数调用父类的构造函数,将父类的实例的属性和函数绑定到子类的this中,另一方面,通过改变子类的prototype属性,继承父类的原型对象上的属性和函数。
优点:
1.既能继承父类实例的属性和函数,又能继承原型对象的属性和函数。
2.即是子类的实例,又是父类的实例。
3.不存在引用属性共亨的问题
4.可以向父类的构造函数中传递参数:call函数可以向父类的构造函数中传递参数。
4.5寄生组合继承
为了解决父类的构造函数被调用两次的问题,
inheritPrototype(SubType, SuperType);
5.ES5和ES6继承区别
ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this)).
ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。
ES5的继承时通过原型或构造函数机制来实现。
ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。
6.如何判断一个变量是否数组?
1)instanceof arr instanceof Array
2)constructor arr.constructor === Array
3)Array.isArray()
4)Object.prototype.toString.call() Object.prototype.toString.call(arr) === '[object Array]'
5)arr.__proto__===)Array.prototype
6)Object.getPrototypeOf() Object.getPrototypeOf(arr) === Array.prototype
7)isPrototypeOf() Array.prototype.isPrototypeOf(arr
7.Null 和 undefined 的区别?
设计JS的作者最初设计的是null,null是借鉴了java里的null,但null有一些问题,而且null会被隐式转化成0,很不容易发现错误。而且后来作者觉得表示无的最好不要是对象。。所以设计出来undefined,undefined不会隐式转换成0,而且是一个基本类型。
7.call bind apply的区别?
1)bind返回对应函数, 便于稍后调用; apply, call则是立即调用。
2)call和bind传递的参数列表,bind也可以分多次传入,而apply传递数组
call源码
Function.prototype.call = function(ctx) {
ctx = ctx || window // 判断context是否存在,存在则为context 不存在则赋予全局作用域
ctx.fn = this // 谁调用这个函数,this就指向谁
var arg = [] // 存储参数
for(let i =0; i<arguments.length;i++) { // 将所有的参数都push到arg里面
arg.push('arguments[' + i + ']')
}
var result = ctx.fn(...array)
delete ctx.fn // 删除属性
return result // 返回result
}
8.防抖节流的概念?实现防抖和节流。
防抖在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时,重新出发定时器。search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
function debounce(fn,denay,immediately=false){
let timer =null;// //使用闭包,缓存变量
var this=_this;
const args=arguments;
return function(){
if(timer) clearInterval(timer);
if(immediately){
let callNow=!timer;
timer=setTimeout(()=>{
timer=null;
},denay);
if(callNow){
fn.apply(this,args)
}
}else{
timer=setTimeout(()=>{
fn.apply(this,args);
},denay)
}
}
}
节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。鼠标不断点击触发,mousedown(单位时间内只触发一次)监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
function throttle(fn,denay,immediately=false){
let timer=0;//使用闭包,缓存变量
const args=arguments;
var this=_this
return function(){
if(!timer){
time=Date.now();
if(immediately){
fn.apply(_this,args)
}
return ;
}
if(Date.now()-time>denay){
fn.apply(this,args);
time=Date.now();
}
}
}
节流是为了限制函数的执行次数,而防抖是为了限制函数的执行时机。
9.深拷贝、浅拷贝的区别?如何实现深拷贝和浅拷贝?
1)浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
实现方法:Object.assign() concat() 扩展运算符
2)深拷贝:不管原数据中值是什么类型的数据,拷贝后的新数据跟原数据是相互独立,没有关联的。
实现方法:利用json数据和json字符串之间的转换Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。
JSON缺点:1..取不到值为undefined的键
2.NAN和无穷转变为null
3. 无法获取自己原型链上的内容,只能获取Object原型内容
4.date对象转为date字符串
递归
function deepClone(obj){
// 判断当前对象是对象还是数组
let result = Array.isArray(obj) ? [] : --{};
if(obj && typeof obj === "object"){
for(let key in obj){
// 判断是否为自身属性
if(obj.hasOwnProperty(key)){
if(obj[key] && typeof obj[key] === "object"){
//子元素为对象,进入递归
result[key] = deepClone(obj[key]);
}else{
result[key] = obj[key];
}
}
}
}
return result;
}
10.对比一下var、const、let。
1)var是ES5提出的,let和const是ES6提出的。
2)var声明的变量存在变量提升现象,let和const声明的变量不存在变量提升现象(遵循:“先声明,后使用”的原则,否则会报错)。
3)var允许重复声明同一个变量,let和const在同一作用域下不允许重复声明同一个变量。
4)var声明的变量不存在块级作用域(是函数级作用域),let和const声明的变量存在块级作用域并且声明的变量只在所声明的块级作用域内有效。
5)var不存在暂时性死区,let和const存在暂时性死区。(let/const 命令会使区块形成封闭的作用域。若在声明之前使用变量,就会报错。
总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的)
6)var声明的变量在window上,而let和const不在
11.ES6新特性有哪些?
1)新增关键字 let const
2)箭头函数:this 是静态的,始终指向函数声明所在作用域下的this的值
3)函数默认参数值(function sum(a,b=5){ return a+b})
4) 运算符…(分散、合并)
5)对象词法扩展(简写变量,忽略function关键字)
6)支持二进制,八进制字面量
7)解构赋值
8)对象超类
9)模板语法
10)for 0f(遍历值) 和for in(遍历键)
11)Map 和WeakMap 一个对象由多个 key-val 对构成,在 Map 中,任何类型都可以作为对象的 key()
12)set 和SetMap :Set对象是一组不重复的值,重复的值将被忽略,值类型可以是原始类型和引用类型:
13)类
14)Symbol:新数据类型,他的值唯一,不可变
15)迭代器iterator
16)Gnerators
17)Promise
18)模块:export import
12.箭头函数和普通函数区别是什么?
1)箭头函数都是匿名函数
2)箭头函数不能用于构造函数,不能使用new
3)this的指向不同:在普通函数中,this总是指向调用它的对象,如果用作构造函数,this指向创建的对象实例。箭头函数中的this是静态的,this的指向永远是声明时所在作用域下的this的值,它并没有自己的this,call apply bind也没有办法改变this的指向
4)箭头函数不具有prototype原型对象。
5)箭头函数不绑定arguments,取而代之用rest参数…解决
13.使用new创建对象的过程是什么样的?
1 )在内存中创建一个新的空对象。
2)让this指向这个新的对象。
3 )执行构造函数里面的代码,给这个新对象添加属性和方法。
4
)返回这个新对象(所以构造函数里面不需要return )。
14.this指向系列问题。
,this的意思为“这个;当前”,是一个指针型变量,它动态指向当前函数的运行环境。
1)箭头函数中:this->声明时所在作用域下的this
2)在全局作用域中:this->winsdow
3)在普通函数中:this->调用者
4)事件绑定中:this->事件源
5)定时器:this->window
6)构造函数中:this->实例化对象
15.谈谈对闭包的理解?什么是闭包?闭包有哪些应用场景?闭包有什么缺点?如何避免闭包?
1)什么是闭包
闭包是一个函数加上创建函数的函数的作用域的连接,闭包关闭了函数的自由变量
function fun(){
var a=10;
return function fuu(){
console.log(a)//a是自由变量
}
}
2)闭包的应用场景
封装功能时,函数防抖、函数节流、函数柯里化、给元素伪数组添加事件需要使用元素的索引值。
3)闭包的缺点
比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。
4)闭包优点
访问其他函数内部变量,延长变量的生命周期,避免定义全局变量所造成的污染
5)如何避免闭包引起的内存泄漏
在退出函数之前,将不使用的局部变量赋值为null
16.JS作用域
1)什么是作用域
是在运行时代码中某些特定部分的变量、函数、和对象的可访问性。作用域决定了代码区块中变量和其它资源的可见性,分为局部作用域和全局作用域
2)自由变量:
当前作用域没有定义的变量,值向父级作用域寻找。要到创建函数的的那个域去找。创建,不是调用。在 A 中作用域要用到的变量 x,并没有在 A 中声明,要到别的作用域中找到他,这个变量 x 就是自由变量
3)作用域链:由子级作用域返回父级作用域中寻找变量,就叫做作用域链