阿里妈妈事业群-电话面

### 阿里妈妈事业群-电话面

#### 判断是不是数组

> `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

>

> ![img](https://public.shiguanghai.top/blog_img/webp.jpg)

>

#### 原型链怎么实现继承

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

>

> 具体例子:

>

> ```js

> // 定义动物类

> function Animal (name) {属性、实例方法}

> // 原型方法

> Animal.prototype.eat = function () {}

> // 原型链继承

> function Cat (food) {}

> Cat.prototype = new Animal()

> Cat.prototype.name = 'cat'

> ```

>

> 除此之外,构造继承

>

> ```js

> function Cat (name) {

>       Animal.call(this)

>       this.name = name

> }

> ```

>

> 实例继承

>

> ```js

> 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循环递归:

>

> ```js

> 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时的作用域链

>

> ```js

> (function(window, undefined) {

>   window.jQuery = window.$ = jQuery

> })( window )

> ```

>

> 一些简单的CRUD操作

>

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

>

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

>

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

>

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

>

> remove():移除元素

#### this 指向问题 举几个例子

> - 代码 1 执行fn()函数时,实际上就是通过对象 o 来调用的,所以 this 指向对象 o

>

> ```js

> // 代码 1

> var o = {

>   fn () {

>     console.log(this)

>   }

> }

> o.fn() // o

> ```

>

> - 代码 2 也是同样的道理,通过实例 a 来调用,this 指向类实例 a。

>

> ```js

> // 代码 2

> class A {

>   fn () {

>     console.log(this)

>   }

> }

> var a = new A()

> a.fn() // a

> ```

>

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

>

> ```js

> // a.fn()

> var fun = a.fn

> fun() // ?

> ```

>

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

>

> - 代码 3 则可以看成是通过全局对象来调用,this 会指向全局对象(严格模式下指向 undefined)

>

> ```js

> // 代码 3

> function fn () {

>   console.log(this)

> }

> fn() // 浏览器:Window;Node.js:global

> ```

>

> - 代码 4 forEach() 有两个参数,第一个参数是回调函数,第二个是 this 指向的对象,这里只传入了回调函数,第二个参数没有传入,默认为 undefined,所以 this 指向全局对象

>

> ```js

> // 代码 4

> var dx = {

>   arr: [1]

> }

> dx.arr.forEach(function() {

>   console.log(this) // 浏览器:Window;Node.js:global

> })

> ```

>

> - 代码 5 ES6 新加入的箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。可以简单地理解为**箭头函数的 this 继承自上层的 this**,但在全局环境下定义仍会指向全局对象。

>

> ```js

> // 代码 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 或参数将无法再被改变。

>

> ```js

> 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. 在对象初始化后

>

> ```js

> // 对象初始化时

> 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对象,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

>

> ```js

> 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_40918145/article/details/117951175