阿里妈妈事业群-电话面
判断是不是数组
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 的区别
- Proxy 更为强大。Object.defineProperty() 只能监视属性的读写,Proxy 能监视到更多对象操作,如 deleteProperty、has等等。
- Proxy 更好的支持数组对象的监视。以往想通过 Object.defineProperty() 去监视数组操作,最常见的一种方式时通过重写数组的操作方法(Vue.js 2.x 使用的方法)大体的思路就是通过自定义的方法去覆盖掉数组原先对象的push、shift等方法以此去劫持对应这个方法调用的过程。但 Proxy 内部会自动根据 push 等操作去推算出它应该所处的下标。
- 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 应用
实现组件:两个框 对象选中到移动到另一个框 逻辑 有哪些需要注意的
还有什么要问我的吗