2021年3月- 杭州-阿里阿里妈妈事业群-电话面面试题

阿里妈妈事业群-电话面

判断是不是数组

Array.isArray(a)

a instanceof Array

a.constructor === Array

oBject.prototype.toString.call(a) === '[object Array]'

你提到instanceof就聊一聊instanceof

Instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

例如在表达式 left instanceof right 中 会沿着 left 的原型链查找看是否存在 right 的 prototype 对象

left.__proto__.__proto__... =?= right.prototype

那就说一说原型链

每一个构造函数都有一个prototype属性,指向了它的原型

每一个实例有一个__proto__属性,这个属性指向了它的原型,原型上还有__proto__,指向了它父类的原型,一直到Object.prototype为止,所以Object的原型,是一个对象的终极原型,它的prototype是null

访问实例的一个属性,会从它的实例内部查找,若没有就到它的原型,还没有就继续向父一级原型查找,一直找到Object.prototype位置,没有就返回undefined

在这里插入图片描述

原型链怎么实现继承

假设构造函数 B() 需要继承构造函数 A(),就可以通过将函数 B() 的显式原型指向一个函数 A() 的实例,然后再对 B 的显式原型进行扩展。那么通过函数 B() 创建的实例,既能访问函数 B() 的属性 b,也能访问函数 A() 的属性 a,从而实现了多层继承。

具体例子:

// 定义动物类
function Animal (name) {
     
     属性、实例方法}
// 原型方法
Animal.prototype.eat = function () {
     
     }
// 原型链继承
function Cat (food) {
     
     }
Cat.prototype = new Animal()
Cat.prototype.name = 'cat'

除此之外,构造继承

function Cat (name) {
     
     
	Animal.call(this)
	this.name = name
}

实例继承

function Cat (name) {
     
     
	var instance = new Animal()
	instance.name = name
	return instance
}

通过原型链判断是不是数组

a.__proto__.__proto__... =?= Array.prototype

数组操作元素的方法 -> 主要考察map() forEach() filter()…方法

  • 不改变原数组的方法

IndexOf()/lastIndexOf():返回元素在数组第一次/最后一次出现的索引,从0开始。若不存在返回-1

slice(start, end):索引从start开始截取到索引end结束,没有参数复制整个数组

concat():合并数组,如果参数是数组会被拉平一次再加入到新数组

join():把当前Array的每个元素都用指定字符串连接起来,没有参数默认用’,'连接

toString():返回数组的字符串形式

valueOf():返回数组本身

map(function(elem, index, arr)):对数组的所有成员依次调用一个函数,返回值是一个新的数组。三个参数依次是当前成员、当前位置、数组本身

forEach():与map方法相似,也是遍历数组的所有成员,执行某种操作,一般没有返回值

filter():删选,返回过滤后的新数组

some():只要有一个数组成员的返回值为true,则整个some方法的返回值就是true,否则为false。空数组返回false

every():所有数组成员的返回值都是true才返回true,否则为false。空数组返回true

reduce()/reduceRight():依次处理数组的每个成员,最终累计为一个值

  • 改变原数组的方法

push():向数组的末尾添加若干元素,返回值是改变后的数组长度

pop():删除数组最后一个元素,返回值是删除的元素

unshift():向数组头部添加若干元素,返回值是改变后的数组长度

shift():删除数组第一个元素,返回值是删除的元素

sort():数组排序,默认将所有元素转换成字符串,再按字符串Unicode码点排序,返回值是新数组;如果元素都是数字,按从小到大排序,可以出传入一个回调函数作为参数

reverse():颠倒数组中元素的位置

splice(start, deleteCount, item):stary 未开始的索引,deletecount 表示要移除的数组元素的个数,item 为要添加进数组的元素

展开数组的方法(数组扁平化)

最简单循环加递归:循环中判断,如果子元素是数组则递归,不是则push到新数组

arr.toString().split(',')

arr.join(',').split(',')

利用reduce+concat循环递归:

arr.reduce(function(init, item) {
     
      
	return init.concat(item是数组循环 不是返回)
}, [])

some+扩展运算符...arr

some+Array.from(arr)

some+[].slice.call(arr)

jQuery 是函数吗、操作数组对象、常用方法

jQuery源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染。然后通过传入window对象参数,可以使window对象作为局部变量使用,同样的传入undefined参数,可以缩短查找undefined时的作用域链

(function(window, undefined) {
     
     
window.jQuery = window.$ = jQuery
})( window )

一些简单的CRUD操作

append():父元素将子元素添加到末尾

prepend():父元素父元素将子元素添加到开头

after():添加元素到元素后面

before():添加元素到元素前面

remove():移除元素

this 指向问题 举几个例子

  • 代码 1 执行fn()函数时,实际上就是通过对象 o 来调用的,所以 this 指向对象 o
// 代码 1
var o = {
     
     
  fn () {
     
     
    console.log(this)
  }
}
o.fn() // o
  • 代码 2 也是同样的道理,通过实例 a 来调用,this 指向类实例 a。
// 代码 2
class A {
     
     
  fn () {
     
     
    console.log(this)
  }
}
var a = new A()
a.fn() // a

但是要注意的是,ES6 下的 class 内部默认采用的是严格模式,这类问题如果改变一下最后:

// a.fn()
var fun = a.fn
fun() // ?

而严格模式下不会指定全局对象为默认调用对象,所以答案是 undefined。

  • 代码 3 则可以看成是通过全局对象来调用,this 会指向全局对象(严格模式下指向 undefined)
// 代码 3
function fn () {
     
     
  console.log(this)
}
fn() // 浏览器:Window;Node.js:global
  • 代码 4 forEach() 有两个参数,第一个参数是回调函数,第二个是 this 指向的对象,这里只传入了回调函数,第二个参数没有传入,默认为 undefined,所以 this 指向全局对象
// 代码 4
var dx = {
     
     
  arr: [1]
}
dx.arr.forEach(function() {
     
     
  console.log(this) // 浏览器:Window;Node.js:global
})
  • 代码 5 ES6 新加入的箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。可以简单地理解为箭头函数的 this 继承自上层的 this,但在全局环境下定义仍会指向全局对象。
// 代码 5
var arrow = {
     
     fn: () => {
     
     
  console.log(this)
}}
arrow.fn() // 浏览器:Window;Node.js:global
  • 代码 6 改变 this 指向的常见 3 种方式有 bind、call 和 apply。call 和 apply 用法功能基本类似,都是通过传入 this 指向的对象以及参数来调用函数。区别在于传参方式,前者为逐个参数传递,后者将参数放入一个数组,以数组的形式传递。bind 有些特殊,它不但可以绑定 this 指向也可以绑定函数参数并返回一个新的函数,当调用新的函数时,绑定之后的 this 或参数将无法再被改变。
function getName() {
     
     console.log(this.name)}
var b = getName.bind({
     
     name: 'bind'})
b()
getName.call({
     
     name: 'call'})
getName.apply({
     
     name: 'apply'})

由于 this 指向的不确定性,所以很容易在调用时发生意想不到的情况。在编写代码时,应尽量避免使用 this,比如可以写成纯函数的形式,也可以通过参数来传递上下文对象。实在要使用 this 的话,可以考虑使用 bind 等方式将其绑定。

箭头函数实现原理 为什么没有this指向

箭头函数中没有 this 的机制,不会改变 this 的指向;它的 this 是继承而来的,默认指向宿主对象,而不是执行时的对象。

箭头函数和普通函数相比:

  • 不绑定 arguments 对象,也就是说在箭头函数内访问 arguments 对象会报错
  • 不能用作构造器,也就是说不能通过关键字 new 来创建实例
  • 默认不会创建 prototype 原型属性
  • 不能用作 Generator() 函数,不能使用 yeild 关键字

提到 arguments

arguments 是具有数组某些特性的「类数组」(伪数组)

每个函数都有一个 Arguments 对象实例 arguments,它引用着函数的实参,可以用数组下标的方式’[]'引用arguments的元素

使用场景:有时候使用闭包来实现一次性函数,但是并不确定传参个数,可以在内部使用apply(this, arguments)的方式来取实参

getter setter 提到了 Proxy

getter 函数:获取 object 对象的属性值

setter 函数:对 object 对象的属性进行赋值

通常有两种手段可以对 object 对象存、取值:1. 在对象初始化时、2. 在对象初始化后

// 对象初始化时
var o = {
     
     
key: 0,
get getkey () {
     
      return this.key }
set setkey (value) {
     
      this.key = value }
}
// 对象初始化后
var o = {
     
      key: 0 }
o.prototype.__defineGetter__('key', function() {
     
      return this.key })
o.prototype.__defineSetter__('key', function(value) {
     
      this.key = value })

这里提到了 ES6 的 Proxy对象,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

const person = {
     
     
name: 'sgh',
age: 21
}

// 构建一个代理对象 第一个参数:需要代理的目标对象
const personProxy = new Proxy(person, {
     
     
// 监视属性读取
get (target, property) {
     
     } // 代理的目标对象 外部访问的属性名
// 监视属性设置
set (target, property, value) {
     
     } // 代理的目标对象 外部访问的属性名 写入的属性名
})

get方法内部的正常逻辑:先判断代理目标对象是否存在这样一个属性,如果存在就返回一个对应的值,反之如果不存在则返回undefined或者是一个默认值。

set方法内部的正常逻辑:为代理目标设置指定的属性,这里我们可以先做一些数据校验,如果设置的是age,值就必须为数字,否则报错。完成以后尝试给代理对象age设置为一个字符串,此时就会报错,如果设置的是一个正常的数字,结果就可以设置到目标对象上。

proxy 和 defineProperty 的区别

  1. Proxy 更为强大。Object.defineProperty() 只能监视属性的读写,Proxy 能监视到更多对象操作,如 deleteProperty、has等等。
  2. Proxy 更好的支持数组对象的监视。以往想通过 Object.defineProperty() 去监视数组操作,最常见的一种方式时通过重写数组的操作方法(Vue.js 2.x 使用的方法)大体的思路就是通过自定义的方法去覆盖掉数组原先对象的push、shift等方法以此去劫持对应这个方法调用的过程。但 Proxy 内部会自动根据 push 等操作去推算出它应该所处的下标。
  3. Proxy 是以非侵入的方式监管了对象的读写。也就是说已经定义好的对象,不需要对对象本身做任何操作就可以监视到内部成员的读写,而 Object.defineproperty() 就要求我们必须通过特定方式单独定义对象中需要被监视的属性。

提到ES6,var let const 区别

const 操作对象 怎么添加对象

js 编译 ->提到了webpack typescript

Babel编译平台 了解吗 你用的什么-> presets

loader实现原理

webpack 打包流程

webpack 构建 开发性能优化

vue computed watch区别

vuex 基本结构

vuex mutation 和 action 区别,为什么不都归到 action

vuex action 应用

实现组件:两个框 对象选中到移动到另一个框 逻辑 有哪些需要注意的

还有什么要问我的吗

猜你喜欢

转载自blog.csdn.net/weixin_40599109/article/details/115209355
今日推荐