「一」如何理解JS的继承?
1. 基于 原型 的继承
- 将子类的原型指向父类的实例对象来实现父类属性和方法的继承;
- 因为父类实例对象的构造函数constructor指向了父类原型,所以需要将子类原型的构造函数constructor指向子类构造函数
代码举例
function Animal(name,color){
this.name = name
this.color = color
}
Animal.prototype.canRun = function(){
console.log(`${this.name} can run!`)
}
function Dog(name,color,speak){
Animal.call(this, name, color) // 关键点
this.speak = speak
}
Dog.prototype = new Animal()
Dog.prototype.canSpeak = function(){
console.log(this.speak)
}
let dog1 = new Dog('小白','white','汪汪~')
2. 基于 class 的继承
class实现继承的核心在于使用extends
表明继承自哪个父类,并且在子类构造函数中必须调用super
,它可以被看成Animal.call(this, name, color)
代码举例
class Animal{
constructor(name,color) {
this.name = name
this.color = color
}
canRun(){
console.log(`${this.name} can run!`)
}
}
class Dog extends Animal{
constructor(name,color,speak) {
super(name,color) // 等价于Animal.call(this, name, color)
this.speak = speak
}
canSpeak(){
console.log(this.speak)
}
}
let dog2 = new Dog('二哈','black','汪!')
「二」闭包
1. 什么是闭包
在一个函数内定义一个内部函数,并将内部函数返回,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包(或者将内部函数成为闭包)。
也有些人定义闭包的概念为:在一个函数内部定义一个函数,那么这个内部函数就被称为“闭包”。
代码举例
function f1(){
let a1=1;
function f2(){
console.log(a1)
}
return f2
}
let result=f1()
result();//1
如上图例子,f2被称为闭包。
2. 闭包的用途
- 通过闭包,外部环境可以读取函数内部的变量。 正常情况下外部环境无法访问到f1函数里变量,但是通过上诉例子,在外部调用result时是可以拿到f1里a1变量的,比如
function f1(){
let a1=1;
function f2(){
return a1;
}
return f2
}
let result=f1()
result();
- 让这些变量的值始终存在内存中。不会在f1调用后被自动清除。
- 使用闭包,内部函数变量不会污染外部变量,规避冲突
3. 闭包的缺点
- 局部变量会常驻内存,造成内存溢出(有一块内存空间被长期占有而不被释放),解决方法是,在退出函数之前,将不使用的局部变量全部删除。 正常情况下,f1函数的变量在f1()执行完成退出当前执行环境时,会被垃圾回收收回。但是由于f2引用了它,而f2又赋值给了result变量,会标记f2被引用了一次,所以f2会在内存里(这里是正常的),f1中变量由于存在于f2函数的closure对象里(闭包里变量的集合)。 f2一直存在内存中,那么f2中的闭包变量也就会一直存在内存中,这些变量造成了内存溢出。
面试题
这里有一个面试题,如果直接使用f1()()调用三次,和result()调用三次,会发现打印的a1的值是不同的。
这个原因,一是闭包,二是我上诉说的垃圾回收机制造成的。
let result=f1()将,f2函数赋值给了result,f2函数被标记了一次引用,执行完result()之后,f2还存在内存中,包括f2中的闭包变量a1,所以多次调用result(),时打印的a1++的值是从闭包变量这个作用域中取得的。这个值又常驻内存,所以会a1的值会变化。
而直接调用f1()()时,没有对f2进行赋值,虽然f2依然有闭包变量集,但是f2没有赋值,被标记次数为0,调用完就会自动清除。所以a1变量也就随着f2不见了。
「三」call、apply、bind 的用法
this 永远指向最后调用它的那个对象。
call、apply、bind这三个函数的作用是改变一个函数在执行时this的指向。
1. call 的用法
fn.call(xxx, 1, 2, 3)
第一个参数是this
, 后面所有的参数是arguments
Array.prototype.forEach2 = function(fn) {
for (let i = 0; i < this.length; i++) {
fn(this[i], i)
}
}
let arr = [1,2,3]
arr.forEach2.call(arr, (item)=>console.log(item))
打印出来1,2,3
2. apply 的用法
fn.call(xxx, [1, 2, 3])
该方法的语法和作用与call方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
求数组里的的最大值和最小值
const array = [15, 6, 24, 3, 9]
const max = Math.max.apply(null, array)
const min = Math.min.apply(null, array)
console.log(max) // 24
console.log(min) // 3
3. bind 的用法
绑定this和参数
function f1(p1,p2) {
console.log(this, p1, p2)
}
let f2 = f1.bind({name: 'mia'})
f2()
let f3 = f1.bind({name: 'sean'}, 'hello', 'world')
f3()
「四」面试题:如何实现数组去重
假设有数组 array = [1,5,2,3,4,2,3,1,3,4]
写一个函数 unique,使得unique(array) 的值为 [1,5,2,3,4]
也就是把重复的值都去掉,只保留不重复的值。
1. 不使用 Set 实现
方法一 (indexOf())
- 新建一个空数组
- 遍历原数组
- 用indexOf() 判断循环出来的元素在新数组中是否存在,不存在则把数据推送至新数组里面
function unique(array){
let result = []
for(let i=0; i<array.length; i++){
if(result.indexOf(array[i]) === -1){
result.push(array[i])
}
}
return result
}
unique([1,5,2,3,4,2,3,1,3,4]) // [1,5,2,3,4]
方法二 (indexOf() / splice())
- 遍历数组
- 用indexOf() 判断循环出来元素下标是否和元素在数组中的索引是否不相等
- 如果不相等(则说明这是一个重复的元素),则把该元素从数组中删除
- 删除后数组长度变短,所以索引也要减一
function unique(array){
for(let i=0; i<array.length; i++){
if(array.indexOf(array[i]) != i){
array.splice(i, 1)
i-- // 因为每次遇到相同的元素删除后,数组的长度会改变,所以数组的下标也要减一
}
}
return array
}
unique([1,5,2,3,4,2,3,1,3,4]) // [1,5,2,3,4]
方法三 (reduce() / includes())
- reduce()初始化一个空数组
- 用includes() 判断新数组是否包含原数组的元素
- 如果没有,则把数据推送至新数组里面
function unique(array){
return array.reduce((result,item)=>{
if(!result.includes(item)) {
result.push(item)
}
return result
},[])
}
unique([1,5,2,3,4,2,3,1,3,4]) // [1,5,2,3,4]
2. 使用 Set
方法一 (new Set() / Array.from())
- new Set() 去重
- Array.from()转成数组
function unique(array){
let result = new Set(array)
return Array.from(result)
}
unique([1,5,2,3,4,2,3,1,3,4]) // [1,5,2,3,4]
方法二 (new Set() / ... )
function unique(array) {
return [...new Set(array)]
}
unique([1,5,2,3,4,2,3,1,3,4]) // [1,5,2,3,4]
⭐set去重注意 (数组中包含引用类型数据)
ES6中set可以实现数组去重,但是如果数组中的数据类型为引用类型时,是无法实现去重的。不支持对象数组和NaN。
3. 使用了 Map / WeakMap 以支持对象去重的
方法 (new Map())
function unique(array) {
let map = new Map();
let result = [];
for (let i=0; i < array.length; i++) {
if(map.has(array[i])) {
map.set(array[i], true);
} else {
map.set(array[i], false);
result.push(arr[i]);
}
}
return result;
}
unique([1,5,2,3,4,2,3,1,3,4]) // [1,5,2,3,4]
「五」数组排序
给出正整数数组 array = [2,1,5,3,8,4,9,5]
请写出一个函数 sort,使得 sort(array) 得到从小到大排好序的数组 [1,2,3,4,5,5,8,9]
新的数组可以是在 array 自身上改的,也可以是完全新开辟的内存。
不得使用 JS 内置的 sort API
1. 选择排序
let minIndex = (numbers) => {
let index = 0
for (let i = 1; i < numbers.length; i++) {
if (numbers[i] < numbers[index]) {
index = i
}
}
return index
}
let swap = (array, i, j) => {
let temp = array[i]
array[i] = array[j]
array[j] = temp
}
let sort = (numbers) => {
for (let i = 0; i < numbers.length - 1; i++) {
let index = minIndex(numbers.slice(i)) + i
if (index !== i) {
swap(numbers, index, i)
}
}
return numbers
}
2. 快速排序
let quickSort = (arr) => {
if (arr.length <= 1) {
return arr
}
// 得到基准下标和基准数
let pivotIndex = Math.floor(arr.length / 2)
let pivot = arr.splice(pivotIndex, 1)[0]
let left = []
let right = []
for (let i = 0; i < arr.length; i++) {
arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i])
}
return quickSort(left).concat([pivot], quickSort(right))
}
3. 归并排序
let mergeSort = (arr) => {
// 精髓 如果长度是1 就默认排好序
if (arr.length === 1) {
return arr
}
let left = arr.slice(0, Math.floor(arr.length / 2))
let right = arr.slice(arr.length / 2)
return merge(mergeSort(left), mergeSort(right))
}
let merge = (a, b) => {
if (a.length === 0) {
return b
}
if (b.length === 0) {
return a
}
return a[0] > b[0] ?
[b[0]].concat(merge(a, b.slice(1))) :
[a[0]].concat(merge(a.slice(1), b))
}
4. 计数排序
let countSort = (arr) => {
let hashTable = {}, max = 0, result = []
for(let i=0; i<arr.length; i++){ // 遍历数组
!(arr[i] in hashTable) ?
hashTable[arr[i]] = 1 :
hashTable[arr[i]] += 1
if(arr[i] > max){ max=arr[i] }
}
for(let j=0; j<=max; j++){ // 遍历哈希表
if(j in hashTable){
for(let i=0; i<hashTable[j]; i++){
result.push(j)
}
}
}
return result
}