目录
new之后发生了什么
- 在内存中创建空对象,作为将要返回的对象实例。将空对象的原型指向了构造函数的prototype属性
- this指向该对象
- 执行构造函数的代码,给这个空对象添加属性和方法
- 返回这个新对象(所以构造函数不需要return,new之后会自动return)
怎么用构造函数实现class
//构造函数实现类
function Demo(name, age){
//强制用new
//if (!(this instanceof Demo)) {
// return new Demo(name, age);
//}
this.name=name
this.age=age
}
Demo.prototype.f1=function(){
console.log(this.name,this.age)
}
//类
class Demo{
constructor(name,age){
this.name=name
this.age=age
}
f1(){
console.log(this.name,this.age)
}
}
注意:
- class语法的底层还是es5中的构造函数,只是把构造函数进行了一次封装而已。es6 class出现的目的为了让对象原型的写法更加清晰、更像面向对象编程,让JavaScript更加的符合通用编程规范,即大部分语言对于类和实例的写法。
- 基本上,es6的class可以看作是一个语法糖,它的绝大部分功能,es5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
- es5的构造函数Demo,对应es6的Demo类的构造方法constructor
es5的Demo原型上的方法对应es6的除了constructor以外的其他方法。 - 和let一样,es6的class不允许重复定义;而且class不存在变量提升,需要先定义再使用
- 构造函数可以提前调用,但是只有属性没有方法
- 虽然没有特别声明,但是class的方法也是定义在prototype上
继承:
//构造函数实现继承
function Parent(name,age){
this.name=name
this.age=age
this.say = function () {
console.log('my name is '+this.name);
}
}
function Son(score){
Parent.call(this,name,age)
this.score=score
}
Son.prototype=Object.create(Parent.prototype) //Object.create浅拷贝
Son.prototype.constructor=Son
//类实现继承
class Parent{
constructor(name,age){
this.name=name
this.age=age
}
say(){ //不加function
console.log('my name is '+this.name);
}
}
class Son extends Parent{
constructor(name,age,score){
super(name,age)
this.score=score
}
}
var son=new Son('xiaoming','nan',67)
son.say() //my name is xiaoming
若是构造函数有返回值:
return 基本数据类型:对new之后的结果没影响,即会忽略return语句,返回this对象。
return 引用数据类型:new之后返回的是该引用数据类型
function Person2(){
this.age = 28;
return 50;
}
var p2 = new Person2();
console.log(p2.age); // 28
function Person3() {
this.height = '180';
return ['a', 'b', 'c'];
}
var p3 = new Person3();
console.log(p3.height); // undefined
console.log(p3.length); // 3
console.log(p3[0]); // 'a'
静态方法、实例方法和原型方法
静态方法:直接定义在构造函数上的方法,只有构造函数可以调用,实例对象不可以调用
实例方法:定义在构造函数中,通过this添加的方法,只有实例对象可以调用,构造函数不可以调用。
原型方法也算是实例方法,构造函数不能直接调用,而是要通过构造函数名.prototype.方法名
来调用。不同的实例可以共享原型方法。
function Demo(){
this.sing=function(){
console.log('这是实例方法');
}
}
Demo.say=function(){
console.log('这是静态方法');
}
Demo.prototype.dance=function(){
console.log('这是原型方法');
}
var obj=new Demo()
var obj1=new Demo()
console.log(obj.sing===obj1.sing); //false
//console.log(obj.sing()===obj1.sing()); //true,因为调用之后都是console.log(),即使括号里的内容不一样,也是true
console.log(obj.dance===obj1.dance); //false
Demo.say() //这是静态方法
Demo.sing() //Demo.sing is not a function
Demo.dance() //Demo.dance is not a function
Demo.prototype.dance() //这是原型方法
obj.say() //obj.say is not a function
obj.sing() //这是实例方法
new 构造函数() 和new 构造函数的区别
function Demo(name){
this.name=name
this.age=18
this.sing=function(){
console.log('my name is '+this.name);
}
}
let obj=new Demo('xiaoming')
let obj1=new Demo
console.log(obj); //Demo {name: 'xiaoming', age: 18, sing: ƒ}
console.log(obj1); //Demo {name: undefined, age: 18, sing: ƒ}
console.log(obj.age); //18
console.log(obj1.age); //18
obj.sing() //my name is xiaoming
obj1.sing() //my name is undefined
console.log(new Demo().age); //18
console.log(new Demo.age); //Demo.age is not a constructor
注意:
- new 构造函数的时候,当不需要传参时,括号可以省略。没有参数时,带不带括号对 new 出的对象来说,没什么影响。但是new要配合函数调用的 . 操作符使用时,有没有括号就有影响了。
new
的运算优先级要小于.
的运算优先级。new Demo.age
执行顺序是这样的:先执行Demo.age
,此时返回结果为undefined
;后执行new
,因new
后面必须跟构造函数,所以new undefined
会报错。由此看出new Demo.age
代码相当于new (Demo.age)
;new Demo().age
相当于(new Demo()).age
。由此看来, new的构造函数后跟括号不是立即执行,而是为了提升优先级。- 运算符的优先级,
.
运算符和new ...(...)
运算符和函数调用...(...)
是同一优先级(函数调用判断的时候要先看前面有没有new,有new的话要连同new一起),从左到右执行,优先级都高于new...
。所以
new Demo().age中new Demo()和 . 优先级相同,从左向右依次执行,先进行new Demo(),再进行(new Demo()).age
new Demo.age .运算符优先级高于new Demo,所以先进行Demo.age,再new (Demo.age)
new Demo.age() 先进行Demo.age,再进行new (Demo.age)()
new Demo().age() 先进行new Demo(),再进行(new Demo()).age,最后进行((new Demo()).age)()
Object.defineProperty()
Object.defineProperty(obj, prop, desc) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
- obj 需要定义属性的当前对象
- prop 当前需要定义的属性名
- desc 属性描述符
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: true
});
var user ={
name:"tom"
} ;
var count = 12;
//定义一个age 获取值时返回定义好的变量count
Object.defineProperty(user,"age",{
get:function(){
return count;
},
set:function(newVal){
count=newVal+1;
}
})
console.log(user.age);//12 获取值
user.age=145; //设置值
console.log(user.age);//146
console.log(count);//146
console.log(user.age) //146
可选的描述符:
value: 设置属性的值
writable: 值是否可以重写。true | false(默认)
enumerable: 目标属性是否可以被枚举。true | false(默认)
configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false(默认)(configurable特性表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。)
set: 目标属性设置值的方法,为function
get:目标属性获取值的方法,为function
注意:
- 当使用了getter或setter方法,不允许使用writable和value这两个属性(如果使用,会直接报错)
- 当writable: true,configurable: false时,可以修改属性值,也可以修改writable的状态,但是不能修改configurable的状态和enumerable的状态。
override和overload的区别
Overload是重载的意思,Override是覆盖的意思,也就是重写。
重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同,即参数个数或类型不同。
重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。Override是覆盖了一个方法并且对其重写,以求达到不同的作用。
对象删除属性报错的情况
Object.defineProperty() 设置属性,且设置configurable:false
(新定义的属性可以不写,即默认情况为false,要是改写定义的属性,则要加上configurable:false
)
详解
删除属性:
- delete
- Reflect.deleteProperty(target, propertyKey)(和delete用法一样)
- 将属性值设置为undefined
const car = {
color: 'blue',
brand: 'Ford'
}
const prop = 'color'
const newCar = Object.keys(car).reduce((object, key) => {
if (key !== prop) {
object[key] = car[key]
}
return object
}, {})
箭头函数和普通函数的区别
- 箭头函数是匿名函数,且没有原型属性,不能作为构造函数,所以不能用new
- 箭头函数不绑定this,他会捕获在定义时所处的外层执行环境的this作为自己的this
- 箭头函数不绑定arguments,取而代之用rest(
…参数
)解决 - 通过 call() 或 apply() 方法调用一个函数时,只是传入了参数而已,对 this并没有什么影响
- 箭头函数不能当做Generator函数,不能使用yield关键字
注意:
匿名函数也可以做构造函数,箭头函数不能作为构造函数不是因为他是匿名函数,而是因为他没有原型属性和this,所以不能用new
解释call、apply、bind
call、apply和bind都可以改变函数的this指向并传入参数,区别是,call和apply会立即调用函数,而bind不会调用函数。除此之外,apply是以数组的形式传入参数,call和bind则是以每个参数的方式传入参数。
call() , bind() , apply()不能改变箭头函数的this指向,对箭头函数来说,call和apply仅为传参并调用函数
箭头函数调用call、apply、bind之后会发生什么
箭头函数的 this 永远指向其上下文的 this (即其在定义时所处的外层执行环境的this),任何方法都改变不了其指向,如 call() , bind() , apply()
深克隆的几种情况
-
使用
JSON.parse()
和JSON.stringify()
对对象进行深拷贝JSON.parse(JSON.stringify(obj))
缺点:这种方法无法实现对函数、正则、undefined等的克隆
-
Object.assign(target, source)
:只是一层深拷贝 -
拓展运算符:只能实现一层深拷贝
-
最好的方法是自己封装一个基于递归的深拷贝函数
作用域是什么,及其会带来的问题
promise
promise是ES6提供的一种异步管理方式。promise 是一个对象,从其中可以获取异步操作的消息,可以说更像是一个容器,保存着未来才会结束的事件(也就是一个异步的操作)。promise是以链式回调的方式解决传统异步操作的回调地狱的问题。
promise有三种状态
pending:初始状态
fulfilled:意味着操作成功完成
rejected:意味着操作失败
改变状态的方法:(Promise的状态一旦改变,就保持该状态不变)
1)Promise.resolve(): 如果当前状态是pending就会变为resolved
2)Promise.reject(): 如果当前状态是pending就会变为rejected
3)throw error(即抛出异常): 如果当前是pending就会变为rejected
throw new Error('呜呜!失败了');
throw '又失败了!'
注意:
-
一个promise指定多个成功/失败的回调函数,当promise改变为对应状态时都会调用
-
promise的构造函数是同步的,.then调用的异步的。
Promise常用方法
-
Promise.all(静态方法)
处理多个promise对象的状态合集,用于并发请求。接收的参数为包含n个promise对象的数组,返回一个新的promise,当参数中的所有promise对象都成功时,返回的promise才成功,成功回调的返回值为包含所有promise返回值的数组。参数中任何一个promise失败,则会触发失败状态,并将其错误信息作为返回的promise的错误信息。如果传入参数为空,则返回一个成功的promise -
Promise.race(静态方法)
处理多个promise对象的状态合集。接收的参数为包含n个promise对象的数组,返回一个新的promise。参数中的任一个promise成功或失败则立即返回。 -
Promise.resolve(静态方法)
1)参数为Promise对象:直接返回该对象
2)参数为非Promise的值:返回一个fulfilled状态的Promise,值为传入的参数
3)参数为空:返回一个fulfilled状态的Promise,值为undefined
4)参数为thenable(即带有then方法的对象):会立即调用then,并跟随他的状态 -
Promise.reject(静态方法)
1)不管参数为什么,都包装成一个rejected的Promise并返回
2)若传入一个fulfilled的Promise,则返回一个rejected的Promise,值为传入的Promise -
promise.finally(不是静态方法)
该方法参数是一个回调函数,返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
这避免了同样的语句需要在then()和catch()中各写一次的情况。 -
Promise.allSettled()(静态方法)
该方法返回一个在所有给定的promise都已经fulfilled或rejected后的Promise,且状态为fulfilled,返回的值为一个对象数组,其中每个结果对象对应着输入的Promise。对于每个结果对象,都有一个 status 属性。如果它的值为 fulfilled,则结果对象上存在一个 value 。如果值为 rejected,则存在一个 reason。
当有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。 -
promise.catch()
promise.then()的语法糖
注意:
– promise.then()的第二个参数和promise.catch()捕获错误信息时遵循就近原则
– promise.then()方法的第一个回调里抛出异常,第二个回调捕获不到,但后面的catch可以捕获到
promise中间有reject返回什么
如果后面有then或catch能处理该错误,则正常执行,否则抛出异常。
如何中断Promise
当使用.then链式调用时,在中间中断,不再调用后面的回调函数。
方法:在回调函数中返回一个pending状态的Promise
async/await
async/await是一种更优雅的写法。async定义了一个异步函数,并且返回一个Promise。async内部可以使用await,await会暂停异步的执行,如果await后面是一个Promise,只有等Promise完成并返回才会继续执行。
await的返回值
如果await后面是一个Promise, await 将等待 Promise 正常处理完成并返回其处理结果。如果fulfilled的,则返回成功的值,如果rejected,则抛出异常(如果用try…catch…则能处理该错误,并且不抛出,否则抛出异常,并且中断后面的执行)
如果await后面不是一个Promise, await直接输出该值(即await 会把该值转换为已正常处理的Promise,然后等待其处理结果)
底层原理
async/await是 Generator 函数的语法糖,并对 Generator 函数进行了改进。它是基于 Generator 和 promise 实现的。(将 async/await 使用 generator 进行改写的关键是要使用 promise 来实现一个 generator 自执行器。)
以下是它的优点:
- 内置了Generator(生成器函数)的自动执行器(上一次执行后的值作为参数传入下一个next函数就能拿到上一次的返回值了)
- 更好的语义
- async/await代码更加同步,避免了Promise链式回调带来的阅读负担
- Promise传递中间值比较麻烦,而async/await几乎是同步的写法
- 返回值是Promise
redux设计模式
Redux的设计模式
redux是什么
redux是一个专门用于做状态管理的JS库(不是react插件库)。它可以用在react, angular, vue等项目中, 但基本与react配合使用。他的作用是 集中式管理应用中多个组件共享的状态。
什么时候要用到redux
- state并不总是以单向的方式线性流动
- 存在组件需要更新全局状态
- 存在组件需要更新另一个组件的状态
- 存在状态以许多不同的方式更新
- 状态树结构复杂
- 某个状态需要在全局使用或共享(例如角色权限等信息)
redux的工作流程:
redux有三个核心:action,store和reducer。view上的操作通过dispatch一个action,通知到store,但是 store 只是一个仓库,不能自己改变,所以需要其他人帮助,这时 reducer 就出现了。reducer 是一个纯函数,他接收两个参数,当前 State 和收到的 Action。所以我们触发的 action 会让 reducer 做出对应的动作,最终让 store 发生相应的变化, store 发生变化最后就会触发view渲染。
react-redux
redux作为一个通用的状态管理库,它不只针对react,还可以作用于vue等。因此react要想完美的应用redux,还需要封装一层,react-redux就是此作用。react-redux库提供了一个react组件Provider和一个方法connect。
- React-redux中不再使用store.getStore(), store.dispatch(), store.subscribe()
即不用subscribe监听了,可以自动监听,而且可以自动dispatch(在mapDispatchToProps简写为一个对象时) - Redux中是引入store(import store),而react-redux要给容器组件通过props传入store(store={store})。可以用Provider给整个传入store
- react-redux的关键作用:
1) 通过Provider把store注入到全局
2) 通过connect把state和dispatch从容器组件注入到UI组件的props上
设计模式(拓展)
MVC模式是三个模式中的基础,也就是说MVP和MVVM都是基于MVC模式衍生出来的,MVP与MVVM都可以看做应用MVC思想做了部分改动的模式。
-
MVC:Model-View-Controller
1)Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。2)View(视图)是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。3)Controller(控制器)是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。 -
MVP:Model-View-Presenter
作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。 -
MVVM:Model-View-View-Model
mvvm模式将Presener改名为View Model,基本上与MVP模式完全一致,唯一的区别是,它采用双向绑定(data-binding): View的 变动,自动反映在View Model,反之亦然。这样开发者就不用处理接收事件和View更新的工作,框架已经帮你做好了。
react、vue和MVVM
Vue 是一个提供了 MVVM 风格的双向数据绑定的框架。它的核心是 MVVM 中的 VM,也就是 View-Model。 View-Model负责连接 View 和 Model,保证视图和数据的一致性。
在 Vue 中,Model 和 VM,VM 和 和 View 之间都是双向数据绑定,实现方式是数据劫持。
但是在 Vue 中,哪一部分是 VM,哪一部分是 M,其实不太好区分。如果 Vue 的一个实例是 VM,那么 model 是什么?如果 data 是 model,先经过实例中的逻辑改变 data,然后 view 产生变化又不符合 MVVM。或者说 VM 是 Vue 框架实现数据响应的源码,实例中的逻辑是 model 层的逻辑,用于改变 model 。所以,个人认为 Vue 只能说是有 MVVM 风格的框架,不能说是一个 MVVM 框架。
react,单向数据流。本身只是 一个函数 ui = render (data) 官方就这么简单一个公式。react加上状态管理等,可以做 MVVM 风格的开发。
不管是 MVC 还是 MVVM ,具体到实际框架,组成成分之间都不会泾渭分明,几种组成成分之间常常有难以划分的模糊地带。如果忽略划分细节从整体来看,Vue 参考但没有完全遵循 MVVM,React 只是一个 View 层。