【面试】蚂蚁一面

new之后发生了什么

  1. 在内存中创建空对象,作为将要返回的对象实例。将空对象的原型指向了构造函数的prototype属性
  2. this指向该对象
  3. 执行构造函数的代码,给这个空对象添加属性和方法
  4. 返回这个新对象(所以构造函数不需要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)
	}
}

注意:

  1. class语法的底层还是es5中的构造函数,只是把构造函数进行了一次封装而已。es6 class出现的目的为了让对象原型的写法更加清晰、更像面向对象编程,让JavaScript更加的符合通用编程规范,即大部分语言对于类和实例的写法。
  2. 基本上,es6的class可以看作是一个语法糖,它的绝大部分功能,es5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
  3. es5的构造函数Demo,对应es6的Demo类的构造方法constructor
    es5的Demo原型上的方法对应es6的除了constructor以外的其他方法。
  4. 和let一样,es6的class不允许重复定义;而且class不存在变量提升,需要先定义再使用
  5. 构造函数可以提前调用,但是只有属性没有方法
  6. 虽然没有特别声明,但是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

注意:

  1. new 构造函数的时候,当不需要传参时,括号可以省略。没有参数时,带不带括号对 new 出的对象来说,没什么影响。但是new要配合函数调用的 . 操作符使用时,有没有括号就有影响了。
  2. new 的运算优先级要小于 . 的运算优先级。new Demo.age执行顺序是这样的:先执行Demo.age,此时返回结果为undefined;后执行new,因new后面必须跟构造函数,所以new undefined会报错。由此看出new Demo.age代码相当于new (Demo.age)new Demo().age相当于(new Demo()).age。由此看来, new的构造函数后跟括号不是立即执行,而是为了提升优先级
  3. 运算符的优先级,.运算符和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) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

  1. obj 需要定义属性的当前对象
  2. prop 当前需要定义的属性名
  3. 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
注意:

  1. 当使用了getter或setter方法,不允许使用writable和value这两个属性(如果使用,会直接报错)
  2. 当writable: true,configurable: false时,可以修改属性值,也可以修改writable的状态,但是不能修改configurable的状态和enumerable的状态。
    在这里插入图片描述

override和overload的区别

Overload是重载的意思,Override是覆盖的意思,也就是重写。
重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同,即参数个数或类型不同。
重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。Override是覆盖了一个方法并且对其重写,以求达到不同的作用。

对象删除属性报错的情况

Object.defineProperty() 设置属性,且设置configurable:false(新定义的属性可以不写,即默认情况为false,要是改写定义的属性,则要加上configurable:false
详解
删除属性:

  1. delete
  2. Reflect.deleteProperty(target, propertyKey)(和delete用法一样)
  3. 将属性值设置为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
}, {})

箭头函数和普通函数的区别

  1. 箭头函数是匿名函数,且没有原型属性,不能作为构造函数,所以不能用new
  2. 箭头函数不绑定this,他会捕获在定义时所处的外层执行环境的this作为自己的this
  3. 箭头函数不绑定arguments,取而代之用rest(…参数)解决
  4. 通过 call() 或 apply() 方法调用一个函数时,只是传入了参数而已,对 this并没有什么影响
  5. 箭头函数不能当做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()

深克隆的几种情况

  1. 使用JSON.parse()JSON.stringify()对对象进行深拷贝

    JSON.parse(JSON.stringify(obj))
    

    缺点:这种方法无法实现对函数、正则、undefined等的克隆

  2. Object.assign(target, source):只是一层深拷贝

  3. 拓展运算符:只能实现一层深拷贝

  4. 最好的方法是自己封装一个基于递归的深拷贝函数

作用域是什么,及其会带来的问题

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 '又失败了!'

注意:

  1. 一个promise指定多个成功/失败的回调函数,当promise改变为对应状态时都会调用

  2. promise的构造函数是同步的,.then调用的异步的。

Promise常用方法
  1. Promise.all(静态方法)
    处理多个promise对象的状态合集,用于并发请求。接收的参数为包含n个promise对象的数组,返回一个新的promise,当参数中的所有promise对象都成功时,返回的promise才成功,成功回调的返回值为包含所有promise返回值的数组。参数中任何一个promise失败,则会触发失败状态,并将其错误信息作为返回的promise的错误信息。如果传入参数为空,则返回一个成功的promise

  2. Promise.race(静态方法)
    处理多个promise对象的状态合集。接收的参数为包含n个promise对象的数组,返回一个新的promise。参数中的任一个promise成功或失败则立即返回。

  3. Promise.resolve(静态方法)
    1)参数为Promise对象:直接返回该对象
    2)参数为非Promise的值:返回一个fulfilled状态的Promise,值为传入的参数
    3)参数为空:返回一个fulfilled状态的Promise,值为undefined
    4)参数为thenable(即带有then方法的对象):会立即调用then,并跟随他的状态

  4. Promise.reject(静态方法)
    1)不管参数为什么,都包装成一个rejected的Promise并返回
    2)若传入一个fulfilled的Promise,则返回一个rejected的Promise,值为传入的Promise

  5. promise.finally(不是静态方法)
    该方法参数是一个回调函数,返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
    这避免了同样的语句需要在then()和catch()中各写一次的情况。

  6. Promise.allSettled()(静态方法)
    该方法返回一个在所有给定的promise都已经fulfilled或rejected后的Promise,且状态为fulfilled,返回的值为一个对象数组,其中每个结果对象对应着输入的Promise。对于每个结果对象,都有一个 status 属性。如果它的值为 fulfilled,则结果对象上存在一个 value 。如果值为 rejected,则存在一个 reason。
    当有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。

  7. 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思想做了部分改动的模式。

  1. MVC:Model-View-Controller
    在这里插入图片描述
    1)Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
      通常模型对象负责在数据库中存取数据。

    2)View(视图)是应用程序中处理数据显示的部分。
      通常视图是依据模型数据创建的。

    3)Controller(控制器)是应用程序中处理用户交互的部分。
      通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

  2. MVP:Model-View-Presenter
    在这里插入图片描述
    作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。

  3. 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 层。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值