1、var、let和const区别?
var a.有变量的提升 b.没有块级作用域 c.可以重新赋值和更改
let a.有块级作用域,有变量死区
const a.有块级作用域,有变量死区 b.常量,不可更改
2、this的指向问题?
重点:箭头函数的this指向问题:是当前定义的这个函数;箭头函数中是没有自己的this的,指的是自己所处的上下文中的this;箭头函数不支持 arguments 对象
- 全局作用域下输出this 它是window对象
- 构造函数实例化出来的对象,this指向这个对象
- setTimeout的this指向window
- 函数内部的this指向window
bind、apply、call都可以改变this的指向、
bind会返回一个新的函数(节省内存)
3、数组和字符串常用方法?
3.1 数组:
改变原数组的方法?
var arr = []
arr.push(): 尾部新增
arr.pop():尾部删除(返回删除元素的值)
arr.unshift():首部新增
arr.shift():首部删除(返回数组长度)
arr.splice():替换或删除
arr.revere():反转
arr.fill():重新填值
arr.sort():排序(数字从小到大 ->字母排序)
arr.copyWithin():浅复制
不改变原数组的方法?
var arr = []
arr.slice() // 返回:start、end选定的元素
arr.map() // 返回:一个新数组
arr.forEach() // 返回:undefined
arr.every() // 返回:布尔值 举例:return item > 5;(一真则真)
arr.some() // 返回:布尔值 举例:return item > 5;(一假则假)
arr.filter() // 返回:一个新数组,是return值为true条件的集合
arr.entries() // 返回一个数组的迭代对象,该对象包含数组的键值对
arr.find() // 返回的就是找到的第一个元素的值,没有找到就返回-1;
arr.concat(‘1’,[‘2’,‘3’]) // 返回其他新拼接数组
arr.reduce()
reduce用法参考来源:https://www.cnblogs.com/smallpen/p/10249288.html
补充:清空数据?
一、arr = []
二、arr.length = 0
三、arr.aplice(0, arr.length)
3.2 字符串:
4、两个不同的js文件调用问题?
es6模块机制参考来源:https://zhuanlan.zhihu.com/p/33843378
5、原型与原型链?
5、深拷贝与浅拷贝?
类型考虑/Symbol/Date等的构造/循环优化/共用引用优化等等
5、ES6新特性,常用API?
let、const/promise/箭头函数/上下文this的指向/class类/new
5、详解promise?
5.1 promise.resolve是干嘛的?
用于将现有对象转换为Promise对象,从而控制异步流程
5.2 promise.then如何实现链式调用?
then()方法会返回一个新的Promise实例,所以then()方法后面可以继续跟另一个then()方法进行链式调用
5.3 promise.then不返还一个promise还能用then吗?
可以
5.4 promise.finally的作用,如何自己实现finally?
用于指定不管 Promise 对象最后状态如何,都会执行的操作;是then方法的特例
5.5 promise原理?
promise 使用的是回调函数,只不过是把回调封装在了内部,使用方式是一直通过 then 方法进行链式调用。
6、数组引用的方式?
下标法/索引法/布尔法
7、如何获取请求url中的?后的参数?
步骤:
1.indexOf 2.substr 3.split 4.for循环
8、闭包问题及优化?
闭包就是函数外能够读取其他函数内部变量的函数;清空变量(null)
9、事件循环?
参考来源:https://www.jianshu.com/p/12b9f73c5a4f/
10、事件绑定方式?
一. 在DOM元素中直接绑定(写法:原生/自定义)
二. 在JavaScript代码中绑定
三. 绑定事件监听函数(addEventListener()/attachEvent() )
11、事件触发流程?
捕获、监听目标和冒泡
11.1 捕获阶段能终止吗?
可以,用stopPropagation()
12、如何终止冒泡?
标准的DOM方法
e.stopPropagation();
非标准的方式 老版本的IE支持
e.cancelBubble = true;
13、事件委托原理?
事件委托利用事件冒泡(从最深的节点开始,然后逐步向上传播事件)只在他们的父元素上指定一个事件处。
通俗的说:
1 本来该给所有的子元素注册事件,现在只需要给父元素注册事件
2 动态创建的元素也具有该事件
$('#box').delegate('span', 'click', function () {
// 此处的this,真正触发事件的元素
console.log($(this).text());
});
14、js异步方式?
callback/event/promise
setTimeout/await
15、如何实现one绑定事件?
one(type,[data],fn)
16、内存泄漏及解决?
说明:
不再用到的内存,没有及时释放。
JS垃圾回收机制?
标记清除(浏览器端常用),会标记该变量"进入环境",此时变量不能被清除,当离开环境时,变量被标记为"离开环境",垃圾回收可以回收变量占用的空
补充:
$(’#box’).empty();清空,元素本身还在
innerHTML = ‘’; 可能会有内存泄漏,无法清除元素的事件处理函数
$(’#box’).html(’’); 没有内存泄漏的问题
$(’#box’).remove(); 移除
js对象循环引用会导致什么问题?
对象不会被销毁,导致内存泄漏
17、数据类型判断?
一、typeof(适用于基础类型)
二、object.prototype.toString.call方法(最靠谱)
三、instanceof(判断 A 是否为 B 的实例,不常用)
四、toString(原型方式判断)
五、constructor 判断(实例通过constrcutor对象来访问它的构造函数)
六、isArray(数组专用方法判断)
18、new做了什么事情?
- 内存中创建了一个空对象
- 把this指向空对象
- 执行构造函数 (设置属性和方法,可以调用其它方法/函数)
- 最后返回刚刚创建的对象
19、Js与Jquery的相互转化?
待补充~~~
20、继承的几种方式?
先写一个父类Demo
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
this.age = age;
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
一、原型链继承
缺点
子类型无法给超类型传递参数;Child.prototype.say要写在Child.prototype = new Parent(‘father’)之后,不然会被覆盖掉。
特点:
1.非常纯粹的继承关系,实例是子类的实例,也是父类的实例
2.父类新增原型方法/原型属性,子类都能访问到
3.简单,易于实现
缺点:
1.要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
2.无法实现多继承
3.来自原型对象的引用属性是所有实例共享的(详细请看附录代码: 示例1)
4.创建子类实例时,无法向父类构造函数传参
Demo00
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
Demo01
function WangJ() {
this.name = name;
this.color = ['red','orange','green'];
}
WangJ.prototype.tv = '豆瓣儿'
WangJ.prototype.money = function () {
console.log('给公司YIYIYIYIYI投资manymanymany股票了')
}
function WangS(dog) {
this.dog = dog;
}
// 这句话的意思是,将王J的所有属性挂载到王S的原型链上
WangS.prototype = new WangJ();
//通过原型链继承创建出来的两个网红实例都可以继承不了王J原型链上的的tv属性和财富方法
//但是原型的所有方法只能写一次,不能重写(实例指向的是改属性的引用)
WangS.prototype = new WangJ();
var WangHong1 = new WangS('keke1');
WangHong1.name = 'xueying1'
console.log(WangHong1.name)//undefined
WangHong1.color.push('blue')
console.log(WangHong1.color)//["red", "orange", "green", "blue"]
console.log(WangHong1.tv);//male
console.log(WangHong1.dog);//keke1
console.log(WangHong1.money())//给公司YIYIYI投资股票
var WangHong2 = new WangS('keke2');
WangHong2.name = 'doudeer2'
console.log(WangHong2.name)//undefined
console.log(WangHong2.color)//["red", "orange", "green", "blue"]
WangHong2.color.push('gray')
console.log(WangHong1.color)//["red", "orange", "green", "blue", "gray"]
console.log(WangHong2.color)//["red", "orange", "green", "blue", "gray"]
console.log(WangHong2.tv);//male
console.log(WangHong2.dog);//keke1
console.log(WangHong2.money())//给公司YIYIYI投资股票
二、构造函数继承(实例属性继承、伪造对象或经典继承)
缺点:
1.在构造函数中设置方法会影响性能,多个对象会存储多份function;
无法复用一些公用函数。
2.如果对象比较多,方法比较多,可能会出现命名冲突(函数封装方法)
3.不管有多少对象,原型中的方法在内存中只有一份(重点使用)
实例对象的原型p1.proto 是非标准的属性,开发中不要使用
4.实例并不是父类的实例,只是子类的实例
5.只能继承父类的实例属性和方法,不能继承原型属性/方法
6.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
特点:
1.解决了1中,子类实例共享父类引用属性的问题
2.创建子类实例时,可以向父类传递参数
3.可以实现多继承(call多个父类对象)
推荐指数:★★
Demo00
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true核心: 使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Father(){
this.color = ["red", "pink", "yellow"];
}
// 借助call继承
function Son(){
Father.call(this);
Father.apply(this);
}
var s1 = new Son();
Demo01
function WangJ(name, age) {
this.name = name;
this.color = ['red','orange','green'];
}
WangJ.prototype.tv = '豆瓣儿'
WangJ.prototype.money = function () {
console.log('给公司YIYIYI投资股票')
}
function WangS( dog) {
// 这句话的意思是,将王J的所有属性挂载到王S的构造方法上
WangJ.call(this)
this.dog = dog;
}
// 通过构造函数继承创建出来的两个网红实例只能继承王J的基本属性
// 通过构造函数继承创建出来的两个网红实例都不能继承不了王J原型链上的的tv属性和财富方法
var WangHong1 = new WangS( 'keke1');
WangHong1.name='xuey'
WangHong1.color.push('blue')
console.log(WangHong1.color)//["red", "orange", "green", "blue"]
console.log(WangHong1.name)//xueying
console.log(WangHong1.tv);//undefined
console.log(WangHong1.dog);//keke1
console.log(WangHong1.money)//undefined
var WangHong2 = new WangS('keke2');
WangHong2.name='doudeer'
console.log(WangHong2.name)//xueying
console.log(WangHong2.color)//["red", "orange", "green"]
console.log(WangHong2.tv);//undefined
console.log(WangHong2.dog);//keke1
console.log(WangHong2.money)//undefined
三、组合继承(构造+原型)
优点:
弥补了原型和构造函数的缺点,使用原型链继承原型方法, 使用借用构造函数继承实例属性。通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
总结:
1.弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
2.既是子类的实例,也是父类的实例
3.不存在引用属性共享问题
4.可传参
5.函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
推荐指数:★★★★(仅仅多消耗了一点内存)
Demo00
组合继承也是需要修复构造函数指向的
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
Demo01
function WangJ() {
this.name = name;
this.color = ['red','orange','green'];
}
WangJ.prototype.tv = '豆瓣儿'
WangJ.prototype.money = function () {
console.log('给公司YIYIYI投资more股票')
}
function WangS(dog) {
// 这句话的意思是,将J的所有属性挂载到王S的构造方法上
WangJ.call(this)
this.dog = dog;
}
// 这句话的意思是,将王J的所有成员挂载到王S的原型链上
WangS.prototype = new WangJ();
var WangHong1 = new WangS('keke1');
WangHong1.name = 'xueying'
console.log(WangHong1.name)//undefined
WangHong1.color.push('blue')//"red", "orange", "green", "blue"]
console.log(WangHong1.color)
console.log(WangHong1.tv);//male
console.log(WangHong1.dog);//keke1
console.log(WangHong1.money())//给公司YIYIYI投资股票
//两个实例是独立的
var WangHong2 = new WangS('keke2');
console.log(WangHong2)//{name: "", color: Array(3), dog: "keke2"}
WangHong2.name = 'doudeer'
console.log(WangHong2.name)//undefined
WangHong2.color.push('black')
console.log(WangHong2.color)//["red", "orange", "green", "black"]
console.log(WangHong2.tv);//male
console.log(WangHong2.dog);//keke1
console.log(WangHong2.money())//给公司YIYIYI投资股票
console.log(WangHong2)//WangS {name: "doudeer", color: Array(4), dog: "keke2"}
Demo01优化方式一
// 直接把父类的原型对象赋给子类的原型对象
function WangJ() {
this.name = name;
this.color = ['red','orange','green'];
}
WangJ.prototype.tv = '豆瓣儿'
WangJ.prototype.money = function () {
console.log('给公司YIYIYI投资more股票')
}
function WangS(dog) {
// 这句话的意思是,将王J的所有属性挂载到王S的构造方法上
WangJ.call(this)
this.dog = dog;
}
// 这句话的意思是,将王J的所有成员挂载到王S的原型链上
WangS.prototype = WangJ.prototype;
var WangHong1 = new WangS('keke1');
WangHong1.name = 'xueying'
console.log(WangHong1.name)//undefined
WangHong1.color.push('blue')//"red", "orange", "green", "blue"]
console.log(WangHong1.color)
console.log(WangHong1.tv);//male
console.log(WangHong1.dog);//keke1
console.log(WangHong1.money())//给公司YIYIYI投资股票
//两个实例是独立的
var WangHong2 = new WangS('keke2');
WangHong2.name = 'doudeer'
console.log(WangHong2.name)//undefined
WangHong2.color.push('black')
console.log(WangHong2.color)//["red", "orange", "green", "black"]
console.log(WangHong2.tv);//male
console.log(WangHong2.dog);//keke1
console.log(WangHong2.money())//给公司YIYIYI投资股票
// 缺点:父类的构造函数被执行了两次
// 第一次:WangS.prototype = new WangJ();
// 第二次:实例化的时候( WangHong1、 WangHong2)
Demo01优化方式二
<!-- 堪称继承最完美的方式!!! -->
function WangJ() {
this.name = name;
this.color = ['red', 'orange', 'green'];
}
WangJ.prototype.tv = '豆瓣儿'
WangJ.prototype.money = function () {
console.log('给公司YIYIYI投资more股票')
}
function WangS(dog) {
// 这句话的意思是,将王J的所有属性挂载到王S的构造方法上
WangJ.call(this)
this.dog = dog;
}
// 这句话的意思是,将王J的所有成员挂载到王S的原型链上
WangS.prototype = Object.create(WangJ.prototype)
WangS.prototype.constructor = WangS
var WangHong1 = new WangS('keke1');
WangHong1.name = 'xueying'
console.log(WangHong1.name)//undefined
WangHong1.color.push('blue')//"red", "orange", "green", "blue"]
console.log(WangHong1.color)
console.log(WangHong1.tv);//male
console.log(WangHong1.dog);//keke1
console.log(WangHong1.money())//给公司YIYIYI投资股票
//两个实例是独立的
var WangHong2 = new WangS('keke2');
WangHong2.name = 'doudeer'
console.log(WangHong2.name)//undefined
WangHong2.color.push('black')
console.log(WangHong2.color)//["red", "orange", "green", "black"]
console.log(WangHong2.tv);//male
console.log(WangHong2.dog);//keke1
console.log(WangHong2.money())//给公司YIYIYI投资股票
// 缺点:父类的构造函数被执行了两次
// 第一次:WangS.prototype = new WangJ();
// 第二次:实例化的时候( WangHong1、 WangHong2)
四、copy继承(深、浅)
深copy(所有成员(属性、方法))、浅copy(属性)
深拷贝,重新开辟一块内存空间,如果对象的成员是复杂类型,会把复杂类型的对象也拷贝一份
特点:
支持多继承
缺点:
1.效率较低,内存占用高(因为要拷贝父类的属性)
2.无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
推荐指数:★(缺点1)
Demo
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
Demo01(浅拷贝)
// 浅拷贝(只会把父级最外层的成员继承过来,而子级与父级内层的对象会指向同一个引用)
// 专业滴说,如果对象的成员是复杂类型,不会把复杂类型的对象拷贝一份,会公用父级的复杂对象
function shadowCopy(parent, child) {
for (var key in parent) {
if (!child[key]) {
child[key] = parent[key];
}
}
}
var wj= {
name: '王J',
money: 100000,
cars: ['五菱宏光', '玛莎拉蒂'],
dog: {
name: 'BYD',
age: 2
}
}
var ws= {
name: '王S'
}
shadowCopy(wj, ws);
ws.dog.name = 'wkk';
ws.money = 1;
console.log(ws);
console.log(wj);
Demo02(深拷贝)
// 深拷贝,如果对象的成员是复杂类型,会把复杂类型的对象也拷贝一份
function deepCopy(parent, child) {
for (var key in parent) {
// 判断child中是否有对应的属性
if (child[key]) {
continue;
}
// 判断parent[key] 对象的类型 是数组还是对象
// instanceof
// 先判断数组 ,因为数组也是对象
if (parent[key] instanceof Function) {
child[key] = parent[key];
} else if (parent[key] instanceof Array) {
child[key] = [];
// 把parent[key] 数组 中所有的成员拷贝给child[key]
deepCopy(parent[key], child[key]);
} else if (parent[key] instanceof Object) {
child[key] = {};
// 把parent[key] 对象 中所有的成员拷贝给child[key]
deepCopy(parent[key], child[key]);
} else {
// 拷贝基本类型的属性
child[key] = parent[key];
}
}
}
var wj = {
name: '王J',
money: 100000,
cars: ['五菱宏光', '玛莎拉蒂'],
dog: {
name: 'BYD',
age: 2
},
play: function () {
console.log('打高尔夫');
}
}
var ws = {
name: '王S'
}
deepCopy(wj, ws);
ws.dog.name = 'wangkeke';
console.log(ws);
console.log(wj);
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
五、ES6继承extends
介绍
1
extends
Demo01
class WangJanLin {
};
class WangS extends WangJanLin {
//这句话的意思是,调用父类的constructor
// ES5在这写,必须定义constructor
constructor(x, y, colors) {
super(x, y)
this.colors = colors
}
// 给子类定义一个方法
toString() {
return this.colors + '' + super.toString();//调用父类发tostring
}
}
var WangHong1 = new WangS('颜色1', '颜色2', ['red', 'orange', 'green'])
console.log(WangHong1.colors)
console.log(WangHong1.toString())
var WangHong2 = new WangS('', '', ['red', 'orange', 'green', 'blue'])
console.log(WangHong2.colors)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 创建一个没有实例方法的类(新类/第三方类)
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
Cat.prototype.constructor = Cat; // 需要修复下构造函数
Demo寄生组合式
优点:解决了两次调用的问题
// 创建只继承原型对象的函数
function inheritPrototype(parent, child) {
// 创建一个原型对象副本-创建对象
var prototype = new Object(parent.prototype);
// 设置constructor属性-增强对象
prototype.constructor = child;
//指定对象
child.prototype = prototype;
}
// 父亲类
function Parent() {
this.color = ['pink', 'red'];
}
Parent.prototype.sayHi = function() {
console.log('Hi');
}
// 儿子类
function Child() {
Parent.call(this);
}
inheritPrototype(Parent, Child);
七、原型式继承
思想:基于已有的对象创建对象
Demo
function createAnother(o) {
// 创建一个临时构造函数
function F() {
}
// 将传入的对象作为它的原型
F.prototype = o;
// 返回一个实例
return new F();
}
附录Demo00
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
//实例引用属性
this.features = [];
}
function Cat(name){
}
Cat.prototype = new Animal();
var tom = new Cat('Tom');
var kissy = new Cat('Kissy');
console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // []
tom.name = 'Tom-New Name';
tom.features.push('eat');
//针对父类实例值类型成员的更改,不影响
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"
//针对父类实例引用类型成员的更改,会通过影响其他子类实例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat']
原因分析:
关键点:属性查找过程
执行tom.features.push,首先找tom对象的实例属性(找不到),
那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的
features属性中插入值。
在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。
刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。