点击一个input依次触发的事件
const text = document.getElementById('text');
text.onclick = function (e) {
console.log('onclick')
}
text.onfocus = function (e) {
console.log('onfocus')
}
text.onmousedown = function (e) {
console.log('onmousedown')
}
text.onmouseenter = function (e) {
console.log('onmouseenter')
}
// 依次打印
'onmouseenter'
'onmousedown'
'onfocus'
'onclick'
原生的自定义事件
语法
let event = new Event(typeArg, eventInit);
typeArg:事件名称
eventInit:参数
- “bubbles”,可选,Boolean类型,默认值为 false,表示该事件是否冒泡。
- “cancelable”,可选,Boolean类型,默认值为 false, 表示该事件能否被取消。
- “composed”,可选,Boolean类型,默认值为 false,指示事件是否会在影子DOM根节点之外触发侦听器。
示例:
// 创建一个支持冒泡且不能被取消的look事件 var ev = new Event("look", {"bubbles":true, "cancelable":false}); document.dispatchEvent(ev);
// 事件可以在任何元素触发,不仅仅是document
myDiv.dispatchEvent(ev);
### typeof和instanceof的区别
+ typeof表示是对某个变量类型的检测,基本数据类型除了null都能正常的显示为对应的类型,引用类型除了函数会显示为'function',其它都显示为object。
+ 而instanceof它主要是用于检测某个构造函数的原型对象在不在某个对象的原型链上。
+ 手写实现instanceof
```js
function my_instanceof(left,right){
let proto = Object.getPrototypeOf(left)
whild(true){
if(proto === null ) return false;
if(proto === right.prototype )return true;
proto = Object.getPrototypeOf(proto)
}
}
一句话描述一下this
- 指向最后调用函数的那个对象,是函数运行时内部自动生成的一个内部对象,只能在函数内部使用
函数内的this是在什么时候确定的?
- 函数调用时,指向最后调用的那个对象
JSONP的原理以及手写一个实现
基本原理:主要就是利用 script 标签的src属性没有跨域的限制,通过指向一个需要访问的地址,由服务端返回一个预先定义好的 Javascript 函数的调用,并且将服务器数据以该函数参数的形式传递过来,此方法需要前后端配合完成。
执行过程:
- 1.前端定义一个解析函数(如: jsonpCallback = function (res) {})
- 2.通过params的形式包装script标签的请求参数,并且声明执行函数(如cb=jsonpCallback)
- 3.后端获取到前端声明的执行函数(jsonpCallback),并以带上参数且调用执行函数的方式传递给前端
- 4.前端在script标签返回资源的时候就会去执行jsonpCallback并通过回调函数的方式拿到数据了
缺点:
- 只能进行get请求
优点:
- 兼容性好,在一些古老的浏览器中都可以运行
代码实现:
function jsonp({ url, params = {}, callbackKey = 'cb', callback }){ // 定义本地的唯一callbackId,若是没有的话则初始化为1 JSONP.callbackId = JSONP.callbackId || 1; let callbackId = JSONP.callbackId; // 把要执行的回调加入到JSON对象中,避免污染window JSONP.callbacks = JSONP.callbacks || []; JSONP.callbacks[callbackId] = callback; // 把这个名称加入到参数中: 'cb=JSONP.callbacks[1]' params[callbackKey] = `JSONP.callbacks[${callbackId}]`; // 得到'id=1&cb=JSONP.callbacks[1]' const paramString = Object.keys(params).map(key => { return `${key}=${encodeURIComponent(params[key])}` }).join('&') // 创建 script 标签 const script = document.createElement('script'); script.setAttribute('src', `${url}?${paramString}`); document.body.appendChild(script); // id自增,保证唯一 JSONP.callbackId++; } // 使用 JSONP({ url: 'http://localhost:8080/api/jsonps', params: { a: '2&b=3', b: '4' }, callbackKey: 'cb', callback (res) { console.log(res) } })
说一下回流和重绘
说一下原型链
详细说一下instanceof
V8的垃圾回收是发生在什么时候?
V8引擎帮助我们实现了自动的垃圾回收管理,利用浏览器渲染页面的空闲时间进行垃圾回收。
说一下垃圾回收机制
setTimeout的执行原理(EventLoop,requestAnimationFrame)
this/apply/call
promise(未读)
js封装(未读)
js继承(未读)
js多态(未读)
js类型转换(未读)
js总结
js设计模式(未读)
js上下文(未读)
作用域和闭包(未读)
this全面解析(未读)
深浅拷贝原理(未读)
原型Prototype(未读)
高阶函数(未读)
节流防抖(未读)
手写js系列(未读)
函数的arguments为什么不是数组?如何转化成数组?
因为arguments本身并不能调用数组方法,它是一个另外一种对象类型,只不过属性从0开始排,依次为0,1,2…最后还有callee和length属性。我们也把这样的对象称为类数组。
常见的类数组还有:
1.用getElementsByTagName/ClassName()获得的HTMLCollection
2.用querySelector获得的nodeList
类数组转数组
Array.prototype.slice.call()
function sum(a,b){ const arr = Array.prototype.slice.call(arguments) console.log(arr) }
Array.from()
function sum(a,b){ const arr = Array.from(arguments) console.log(arr) }
ES6展开符
function sum(a,b){ const arr = [...arguments] console.log(arr) }
利用concat+apply
function sum(a,b){ const arr = Array.prototype.slice.apply([],arguments) console.log(arr) }
forEach中return有效果吗?如何中断forEach循环?
在forEach中用return不会返回,函数会继续执行。
let nums = [1, 2, 3];
nums.forEach((item, index) => {
return;//无效
})
中断方法
- 1.使用try监视代码块,在需要中断的地方抛出异常。
- 2.官方推荐方法(替换方法):用every和some替代forEach函数。every在碰到return false的时候,中止循环。some在碰到return true的时候,中止循环
JS判断数组中是否包含某个值
indexOf
此方法判断数组中是否存在某个值,如果存在,则返回数组元素的下标,否则返回-1。
let arr = [1,2,3] let index = arr.indexOf(3) console.log(index)
includes
此方法判断数组中是否存在某个值,如果存在返回true,否则返回false
var arr=[1,2,3,4]; if(arr.includes(3)) console.log("存在"); else console.log("不存在");
find
返回数组中满足条件的第一个元素的值,如果没有,返回undefined
var arr=[1,2,3,4]; var result = arr.find(item =>{ return item > 3 }); console.log(result);
findIndex
返回数组中满足条件的第一个元素的下标,如果没有找到,返回-1
var arr=[1,2,3,4]; var result = arr.findIndex(item =>{ return item > 3 }); console.log(result);
JS中flat—数组扁平化
- let ary = [1, [2, [3, [4, 5]]], 6];// -> [1, 2, 3, 4, 5, 6]
1. 调用ES6中的flat方法
ary = ary.flat(Infinity);
递归
let result = [];
let fn = function(ary) {
for(let i = 0; i < ary.length; i++) {
let item = ary[i];
if (Array.isArray(ary[i])){
fn(item);
} else {
result.push(item);
}
}
}
reduce迭代
function flatten(ary) {
return ary.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
let ary = [1, 2, [3, 4], [5, [6, 7]]]
console.log(flatten(ary))
扩展运算符
//只要有一个元素有数组,那么循环继续
while (ary.some(Array.isArray)) {
ary = [].concat(...ary);
}
模拟实现new
new做了什么
- 1.创建一个新对象,并继承其构造函数的prototype,这一步是为了继承构造函数原型上的属性和方法
- 2.执行构造函数,方法内的this被指定为该新实例,这一步是为了执行构造函数内的赋值操作
- 3.返回新实例(规范规定,如果构造方法返回了一个对象,那么返回该对象,否则返回第一步创建的新对象)
// new是关键字,这里我们用函数来模拟,new Foo(args) <=> myNew(Foo, args)
function myNew(foo, ...args) {
// 创建新对象,并继承构造方法的prototype属性, 这一步是为了把obj挂原型链上, 相当于obj.__proto__ = Foo.prototype
let obj = Object.create(foo.prototype)
// 执行构造方法, 并为其绑定新this, 这一步是为了让构造方法能进行this.name = name之类的操作, args是构造方法的入参, 因为这里用myNew模拟, 所以入参从myNew传入
let result = foo.apply(obj, args)
// 如果构造方法已经return了一个对象,那么就返回该对象,否则返回myNew创建的新对象(一般情况下,构造方法不会返回新实例,但使用者可以选择返回新实例来覆盖new创建的对象)
return Object.prototype.toString.call(result) === '[object Object]' ? result : obj
}
// 测试:
function Foo(name) {
this.name = name
}
const newObj = myNew(Foo, 'zhangsan')
console.log(newObj) // Foo {name: "zhangsan"}
console.log(newObj instanceof Foo) // true
js继承
原型链继承
- 原型链继承的原理很简单,直接让子类的原型对象指向父类实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承
function Parent(name){
this.name = 'aaa';
}
Parent.prototype.getName = function(){
return this.name;
}
function Child(){}
Child.prototype = new Parent();
// 测试
const c1 = new Child()
const c2 = new Child()
c1.name = 'bbb';
console.log(c1.name) // bbb
console.log(c2.name) // bbb
- 缺点:
- 1.由于所有Child实例原型都指向同一个Parent实例, 因此对某个Child实例的父类引用类型变量修改会影响所有的Child实例
- 2.在创建子类实例时无法向父类构造传参, 即没有实现super()的功能
构造函数继承
- 构造函数继承,即在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参
function Parent(){
this.name = name';
}
Parent.prototype.getName = function(){
return this.name;
}
function Child(){
// 执行父类构造方法并绑定子类的this, 使得父类中的属性能够赋到子类的this上
Parent.call(this,'aaa')
}
let c1 = new Child()
let c2 = new Child()
console.log(c1.name) // aaa
console.log(c2.name) // aaa
console.log(c1.getName()) // 报错
- 缺点:
- 继承不到父类原型上的属性和方法
组合式继承
- 既然原型链继承和构造函数继承各有互补的优缺点, 那么我们为什么不组合起来使用呢, 所以就有了综合二者的组合式继承
function Parent(name){
this.name = name;
}
Parent.prototype.getName = function(){
return this.name;
}
function Child(){
Parent.call(this,'aaa')
}
Child.prototype = new Parent()
console.log(c1.name) // aaa
console.log(c2.name) // aaa
console.log(c1.getName()) // aaa
- 缺点:
- 每次创建子类实例都执行了两次构造函数(Parent.call()和new Parent()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅
寄生式组合继承
- 为了解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 构造函数继承
Parent.call(this, 'zhangsan')
}
//原型链继承
// Child.prototype = new Parent()
Child.prototype = Object.create(Parent.prototype) //将`指向父类实例`改为`指向父类原型`
Child.prototype.constructor = Child
//测试
const child = new Child()
const parent = new Parent()
child.getName() // ['zhangsan']
parent.getName() // 报错, 找不到getName()