- 说说JS原型和原型链
原型:函数都有prototype(显示原型)属性,而prototype会自动初始化一个空对象,这个对象就是原型对象
原型对象中会有一个constructor属性,这个属性将指向了函数本身
实例化对象都有一个_proto_(隐式原型)属性,_proto_属性指向原型对象
原型链:从实例对象往上找构造这个实例的相关对象,然后这个关联的对象再往上找,找到创造它的上一级的原型对象,以此类推,一直到object.prototype原型对象终止,原型链结束.
原型链中的原型对象中的内容,是会被不同的实例,所共有的
- ☆call和apply的区别和作用?
apply和call都是调用一个对象的一个方法,用另一个对象替换当前对象。
相同点:方法的含义是一样的,即方法功能是一样的。并且第一个参数的作用是一样的
不同点:call可以传入多个参数、apply只能传入两个参数,所以其第二个参数往往是作为数组形式传入
存在意义:实现(多重)继承
- 继承的方法有哪些?手写ES6 class继承
原型链继承、构造继承、实例继承、拷贝继承、组合继承、寄生组合继承
//定义一个类
class Children{
constructor(skin,language){
this.skin = skin;
this.language = language;
}
say(){
console.log("I'm a Person")
}
}
class American extends Children{
constructor(skin,language){
super(skin,language)
}
aboutMe(){
console.log(this.skin+" "+this.language)
}
}
var m = new American("张三","中文");
m.say();
m.aboutMe();
1)子类没有constructor
子类American继承父类Person,子类没用定义constructor则默认添加一个,并且在constructor中调用super函数,相当于调用父类的构造函数。调用super函数是为了在子类中获取父类的this,调用之后this指向子类。也就是父类prototype.constructor.call(this)
- 子类有constructor
子类必须在constructor方法中调用super方法,否则new实例时会报错。因为子类没有自己的this对象,而是继承父类的this对象。如果不调用super函数,子类就得不到this对象。super()作为父类的构造函数,只能出现在子类的constructor()中,但是super指向父类的原型对象,可以调用父类的属性和方法。
- ☆什么是闭包?闭包有什么作用?
由于在js中,变量到的作用域属于函数作用域,在函数执行后作用域会被清除、内存也会随之被回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数---也就是闭包,便拥有了访问上级作用域中的变量权限,即使上级函数执行完后,作用域内的值也不会被销毁。
闭包解决了什么:在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
由于闭包可以缓存上级作用域,那么就使得函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。以平时使用的Ajax成功回调为例,这里其实就是个闭包,由于上述的特性,回调就拥有了整个上级作用域的访问和操作能力,提高了几大的便利。开发者不用去写钩子函数来操作审计函数作用域内部的变量了。
闭包有哪些应用场景:
闭包随处可见,一个Ajax请求的成功回调,一个事件绑定的回调函数,一个setTimeout的延时回调,或者一个函数内部返回另一个匿名函数,这些都是闭包。简而言之,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都有闭包的身影
闭包的缺陷:由于闭包打破了函数作用域的束缚,导致里面的数据无法清除销毁,当数据过大时会导致数据溢出
- ☆new操作符在创建实例的时候经历了哪几个阶段
new创建了一个对象,共经历了4个阶段:
- 创建一个空对象
- 设置原型链
- 让实例化对象中的this指向对象,并执行函数体
- 判断实例化对象的返回值类型
- 什么是深拷贝和浅拷贝?
- 深拷贝和浅拷贝值针对Object和Array这样的复杂类型
- a和b指向了同一块内存,所以修改其中任意一个值,另外一个值也会随之变化,这是浅拷贝
- a和b指向同一块内存,但是修改其中任意一个值,另外一个调用的变量,不会受到影响,这是深拷贝
- 浅拷贝:“Object.assign()”方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象,它将返回目标对象
- 深拷贝:JSON.parse( )和JSON.stringify( )给了我们一个基本的解决办法。但是函数不能被正确处理
- 列举常用的ES6特性
- let、const
- 箭头函数
- class类的支持
- 字符串模块
- symbol
- Promise
- let、const、var的区别
var声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的
由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明;
let是更完美的var,不是全局变量,具有块级函数作用域,大多数情况不会发生变量提升。
- let声明的变量具有块级作用域
- let生命的变量不能通过window.变量名访问
- 形如for(let x...)的循环是每次迭代都为x创建新的绑定
- let约束了变量提升
- let不允许在相同作用域内重复声明同一个变量名,var是允许的
const定义的常量值,不能够重新赋值,如果值是一个对象,可以改变对象里边的属性值。const变量声明的时候必须初始化
- 简单介绍一下promise和async/await
Promise:
简单来说就是一个容器,里面保存着某个未来才会结束的时间(通常是一个异步操作的结果)
基本语法: let p = new Promise((resolve,reject) => {
//...
resolve('success')
});
p.then(result => {
console.log(result);//success
});
promise共有三个状态: pending(执行中)/success(成功)/rejected(失败)
async/await:
简洁: 异步编程的最高境界就是不关心它是否是异步.async await很好的解决了这一点,将异步强行转换为同步处理.
async/await与promise不存在谁代替谁的说法,因为async、await是寄生于Promise、Generater的语法糖
用法:
1. async和await是配对使用的,await存在于async的内部,否则报错
2. await表示在这里等待一个promise返回,再接下来执行
区别:
- promise是ES6,async、await是ES7
- async/await相对于promise来讲,写法更加优雅
- reject状态
- promise错误可以通过catch来捕捉,建议尾部捕获错误
- async、await既可以用.then又可以用try-catch捕捉
- 什么是面向对象?
面向过程就是分析出解决问题所需要的步骤,然后用函数吧这些步骤一步一步实现,使用的时候一个一个一次调用就可以了
面向对象是把构成问题事务分解成各个对象,建立帝乡的目的不是为了完成一个步骤,而是为了描述某个事务在整个解决问题的步骤中的行为
面向对象的三大特性:
1、封装
隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
2、继承
提高代码复用性;继承是多态的前提。
3、多态
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。
五大基本原则:
1、单一职责原则SRP(Single Responsibility Principle)
类的功能要单一,不能包罗万象,跟杂货铺似的。
2、开放封闭原则OCP(Open-Close Principle)
一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
3、里式替换原则LSP(the Liskov Substitution Principle LSP)
子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
4、依赖倒置原则DIP(the Dependency Inversion Principle DIP)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的是抽象的中国人,而不是你是xx村的。
5、接口分离原则ISP(the Interface Segregation Principle ISP)
设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。