对象相关面试总结

174 阅读5分钟

创建对象的几种方式(类)

构造函数模式

function Person(name,gender,age){
  this.name = name;
  this.gender = gender;
  this.age = age;
  this.sayName = function(){
    console.log("name is " + this.name)
  }
}
let tom = new Person("tom","man",18)

工厂函数模式

function Person(name,gender,age){
  let obj = new Object()
  obj.name = name;
  obj.gender = gender;
  obj.age = age;
  obj.sayName = function(){
    console.log("name is " + obj.name)
  }
  return obj;
}
let tom = Person("tom","man",18)

工厂函数和构造函数区别

1.构造函数没有明显的创建对象过程

这一点是最本质的区别,工厂模式会创建不同的继承自Object的对象,而构造函数模式不会

2.没有return
3.书写风格上首字母大写

new关键字的作用

1.创建一个新的对象obj
2.将构造函数的prototype原型作为新创建对象的proto原型
3.执行构造函数并且绑定this为新创建对象
4.构造函数执行结果是对像则返回执行结果否则返回新创建对象

手写new的pollyfill

function myNew(fn,...rest){
  let obj = Object.create(fn.prototype);
  const res = fn.apply(obj,rest);
  return typeof res === 'object' ? res : obj;
}

class模式

class模式是es6引入的方法

class Person {
  constructor(name,gender,age){
    this.name = name;
    this.gender = gender;
    this.age = age;
  }
  sayName(){
    console.log("name is " + this.name)
  }
}
let tom = Person("tom","man",18)

class模式还提供了get方法,set方法,static静态方法

class Person {
  constructor(name,gender,age){
    this.name = name;
    this.gender = gender;
    this.age = age;
  }
  //获取某一个属性时的改写方法
  get name(){
    return this.name+"getter"
  }
  //设置某一值时的改写方法
  set name(val){
    return this.name+"setter"+val
  }
  //静态方法,只能被类调用,不能被实例调用
  static walk(){
    return "he is walk"
  }
  sayName(){
    console.log("name is " + this.name)
  }
}
let tom = Person("tom","man",18)

对象继承的几种方式

对象继承的原理

对象的继承简单来说就是一个对象可以使用其父对象的属性和方法,和其最相关的知识点就是原型链的概念

原型链

原型链包含了几个不同的小概念:

  1. 构造函数
    构造函数就是用于创建对象的函数,其包含一个属性为“prototype”,即构造函数的原型
  2. 实例对象的原型
    这个就是我们Object.getprototypeof和Object.setprototypeof所得出的原型对象,在浏览器中多表述为_proto_
  3. 这二者的关系
    简单来说就是对象的__proto__等于构造函数的prootype
  4. 原型链
    因为此种对等关系,在a对象使用过程中,如果其构造函数的prototype为b对象的实例,则a继承b对象,在a中可以访问继承而来的属性,从而形成的层层继承链条(在js中,顶层原型为null)

继承的方式

  1. 原型继承
//子对象构造函数
function SubType(){
  this.name = "sub"
}
//父对象构造函数
function SuperType(){
  this.name = "super"
}
//继承(使构造函数的prototype等于父对象实例)
SubType.prototype = new SuperType()
  1. 构造函数模式
//父对象构造函数
function SuperType(name){
  this.man = name
}
//子对象构造函数
function SubType(){
  SuperType.call(this,"super")
  this.name = "sub"
}

这种模式严格来说不算是“继承”,因为这种模式不符合原型链的概念,他只是使用call来执行父对象的构造函数,强行改变this指向,在子对象中添加了父对象的属性

  1. 组合继承

组合继承是为了在构造函数继承的模式下优化原型链所提出的

//父对象构造函数
function SuperType(name){
  this.man = name
}
//子对象构造函数
function SubType(){
  SuperType.call(this,"super")
  this.name = "sub"
}
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType

组合继承就是在构造函数的基础上加了最后两句来操作原型,这两句的作用是:

  1. SubType.prototype = new SuperType()
    这一句很明显是把SuperType实例赋值给子类构造函数的prototype属性,也就是原型链操作,这个操作导致了SubType.prototype.constructor被改写成SuperType
    这一点这么理解:由于SubType.prototype被改写,原始的继承关系也会被改写,而new SuperType()所带来的属性(包括父类的属性)都会覆盖SubType.prototype
  2. SubType.prototype.constructor = SubType
    这一句就是为了弥补上面的副作用,把constructor改会正常的SubType
  1. 寄生组合继承
function SuperType(name){
  this.man = name
}
//子对象构造函数
function SubType(){
  SuperType.call(this,"super")
  this.name = "sub"
}
//创建一个__proto__原型为SuperType.prototype的对象
let obj = Object.create(SuperType.prototype)
//改写构造函数为子类
obj.constructor = SubType
//改写原型
SubType.prototype = obj

这中模式干了两件事 SuperType.call(this,"super") 混合对象,let obj = Object.create(SuperType.prototype) 继承对象,原理理解了其实和组合继承差不多,但是只调用了一次父类

class继承

class SuperType {
  constructor(name){
    this.name = name
  }
}
class SubType extends SuperType {
  constructor(name,age){
    super(name)
    this.age = age
  }
}

其中super关键字为父类构造函数的代写

this

this是一个运行时绑定的值,简单来说就是谁调用函数,函数内的this绑定谁
而箭头函数的this是闭合语法上下文的值,也就是定义时的值,而是用bind,apply,call可以改变绑定

bind,apply,call的区别

  1. bind执行后返回的是一个改变了this指向的函数,而其他两种是直接执行的结果
  2. apply接受的第一个参数是执行上下文,第二个参数是一个参数数组,其他两种第一个参数是执行上下文,被调用函数参数顺序传入
  1. 手写call
Function.prototype.myCall = function(context=window,...rest){
  //这里的this就是需要改变对象的函数
  context.fn  = this;
  const result = context.fn(...rest);
  delete context.fn
  return result;
}

2.手写apply

Function.prototype.myApply = function(context=window,rest=[]){
  if(!Array.isArray(rest)){
    throw Error("必须是数组")
  }
  //这里的this就是需要改变对象的函数
  context.fn  = this;
  const result = context.fn(...rest);
  delete context.fn
  return result;
}

2.手写bind

Function.prototype.myBind = function(context=window,...rest){
  //这里的this就是需要改变对象的函数
  let _this = this
  let Noop = function(){};
  let fToBind = function(){
    return _this.apply(context,rest)
  }
  //支持使用new调用新创建的构造函数
  // 把要被绑定的prototype拿回来
  if(_this.prototype){
    Noop.prototype = _this.prototype
  }
  fToBind.prototype = new Noop()
  return fToBind
}

reflect

  1. reflect的作用
  1. 将一些属于对象内部的方法,放到reflect上,例如Object.defineProperty,现在是Reflect.defineproperty
  2. 改善了一些操作的返回值,例如Object.defineProperty没有找到属性时会报错,Reflect.defineproperty会返回false
  3. 让Object操作都变成函数行为,例如delete obj[name]可以写为 Reflect.deleteProperty(obj, name)
  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法

Proxy

基础用法是let p = new Proxy(target,handler)
target是你要改写的对象,handler是操作options,包含很多操作钩子,get,set之类