如果你最近想要换工作或者巩固一下自己的前端知识基础,不妨和我一起参与到每日刷题的过程中来,如何?
第九天要刷的面试题如下:
-
js中对象的创建方式
-
js中对象的继承方式
-
对this指向的理解
-
对比set和map数据结构
-
如何找到数组中的众数
下面是我自己的理解:
-
在面向对象开发方法中,关于构件型设计模式有五种:抽象工厂、构造器、单例、原型、构造器;这五种模式都可以用来构造js中的对象。
- 但一般来说可以分成下面七种方法:
-
-
字面量形式
-
-
-
工厂模式:简单理解成返回
[Object object]
类型的函数
-
-
function createPerson(name, age) {
const person = {};
person.name = name;
person.age = age;
person.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
return person;
}
-
-
构造函数模式:利用js中函数可以以构造函数方式执行的特性,相比于工厂模式,无需显式的返回一个
[Object object]
变量
-
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
}
-
-
原型模式:可以看成是对构造函数模式的改进,利用了js原型链,使得同一构造函数/工厂创建的对象上的方法可以进行复用,节省内存,提高了效率,但问题在于共享了属性和方法,导致属性修改失去独立性; 重点是prototype
-
function Person() {
}
Person.prototype.name = "Alice";
Person.prototype.age = 25;
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
-
-
构造函数模式组合原型模式:解决了纯原型模式属性不能独立修改的问题;即对于对象上的属性和方法采用了不同的处理策略
-
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
-
-
动态原型模式:从形式上看,将对prototype对象的赋值放到构造函数内部
-
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
这样做的效果见下一题的分析!
-
-
寄生构造函数模式:实现和工厂模式完全相同,但是在js中以构造函数方式运行。所谓寄生指的是:寄生构造函数模式的主要特点是在构造函数内部创建并返回一个新的对象实例,而本来它应该返回this的
-
function Person(name, age) {
var obj = {};
obj.name = name;
obj.age = age;
obj.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
return obj;
}
var person1 = new Person("Alice", 25);
var person2 = Person("Bob", 30);
可以看出来从设计模式
和js中的继承方式
这两个角度去回答这个问题是比较合适的!
-
原型链继承
-
原理:父类的实例作为子类的原型对象
-
优点:简单、父类方法可以复用
-
缺点:子类共享引用型属性、创建子类的时候无法传参,子类无法自定义自己的属性
-
构造函数继承
-
原理:将父类的构造函数的内容赋值给了子类的构造函数
-
优点:避免了引用类型属性被所有实例共享
-
缺点:方法都是在构造函数中定义的,所以每次创建实例就会创建一次方法,即无法复用父类的公共方法;每次创建新的实例的时候都要执行一次父类函数。
-
组合继承
-
原理:结合了原型式继承和构造函数继承
-
优点:可以在子类实例化的时候给构造函数传递参数了;父类的方法也可以被复用;父类的引用类型属性不会被共享
-
缺点:因为结合了两种方法,所以重复调用了两次父类的构造函数,一次是为子类绑定原型对象的时候(需要实例化一个父类对象);一次是使用call方法复用父类构造函数的时候。重复执行构造函数的后果就是同名属性既会成为ownProperty也会在原型链中出现,造成性能上的浪费!
-
原型式继承
-
原理:采用的是原型设计模式的概念,本质上是对对象的一个浅复制
-
优点:可以复用父类上的方法
-
缺点:子类实例化的时候不能传入参数;引用类型属性被子类对象共享
-
寄生式继承
-
原理:在原型式继承的基础之上,对得到的新对象进行属性/方法的增强
-
优点:能够快速的构造出符合期望的对象
-
缺点:无法对方法复用,效率低;所得对象共享引用型属性
-
寄生组合式继承
-
原理:克服了组合继承父类构造函数执行两次的问题(使用的是Object.create)
-
优点:指定子类原型对象的时候无须创建父类的实例对象;只调用了一次父类的构造函数;能够保证原型链的正确性;是最佳方式
-
缺点: 复杂
-
ES6的class语法糖
-
-
函数单独调用:非严格模式下指向window,严格模式下this值为undefined
-
-
-
函数作为对象方法调用:指向此对象(如果this没有被绑定)
-
-
-
函数作为构造器调用:指向创建的新对象
-
-
-
函数以call\apply方式调用:指向指定的值(如果this没有被绑定)
-
-
有序性:map在遍历的时候式有序的,顺序是键插入的顺序;而set是无序的。
-
唯一性:set中的元素作为整体具有唯一性;而map中的key-value作为整体来看的话只需要保证key的唯一性,value可以重复。它们的唯一性都是通过哈希表实现的。
-
值类型:set中的元素可以是任何类型;map中的key和value也可以是任何类型。
-
使用场景:set的功能完全可以由map代替;map用在维护对象之间的关系上,而set常用来去重。
这个题有个坑:众数可能会有多个;不要使用两次遍历,思路如下:
-
需要一个
映射表f
储存元素-个数
关系;需要一个变量maxCount记录目前最大出现次数值
;还需要一个数组mode存储最大出现次数值对应的元素
- 遍历数组,针对其中的每一个元素,做如下判断
-
-
映射表中已经存在,则次数加1,如果不存在则插入表中,并且初始化次数为1
-
-
-
判断此元素的次数是否大于记录的最大次数,如果大于目前的最大次数,则更新maxCount的值,并且将mode的值初始化为:
[此元素]
-
-
-
判断此元素的次数是否等于记录的最大次数,如果等于目前的最大次数,则将其加入到mode中:
mode.push(此元素)
-
-
-
遍历完成之后将mode的值返回
-
-
function findMode (nums) {
const f = {};
let maxCount = 0;
let modes = [];
for (let i = 0; i < nums.length ; i++) {
const _tmp = nums[i];
if(f[_tmp]){
f[_tmp]++;
} else {
f[_tmp] = 1;
}
if (f[_tmp] > maxCount) {
maxCount = f[_tmp];
modes = [_tmp];
} else if (f[_tmp] === maxCount) {
modes.push(_tmp);
}
}
return modes;
}