### 阿里妈妈事业群-电话面
#### 判断是不是数组
> `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 应用
实现组件:两个框 对象选中到移动到另一个框 逻辑 有哪些需要注意的
还有什么要问我的吗