参考:https://blog.csdn.net/zhaolandelong/article/details/88564665
**JS = ES + DOM(文档对象类型) + BOM(浏览器对象类型)**
知识点
1. js数据类型及判断
2. 闭包与作用域
3. this与执行上下文
4. 原型链与继承(原型、构造函数、实例)
5. 异步任务
6. ES6+常用特性
请说出以下代码打印的结果
if (1 == true) { console.log(1); };
if (1 === true) { console.log(2); };
if ([]) { console.log(3); };
if ([] == []) { console.log(4); };
if ([] === []) { console.log(5); };
if (undefined == null) { console.log(6); };
if ('' == null) { console.log(7); };
if (NaN == NaN) { console.log(8); };
答案: 1、3、6
任何与NaN运算的结果都为NaN。NaN属性是代表非数字值的特殊值。
console.log(isNaN(NaN)) //true
console.log(isNaN(10)) //false
console.log(isNaN('20')) //false 将'20'转为数字类型20
console.log(isNaN('abc')) //true
console.log(isNaN(true)) //false 将true转为1
undefined == null 两者都是无效值(js规范,在比较相等性之前,undefined和null不能转化成其他类型的值。undefined和null是相等的)
? undefined === null (false) 两者不是同一数据类型
typeof undefined // undefined
typeof null // object
? '' == null
null: 该值声明的是一个空对象,没有指向任何的内存空间
'':该字符串声明的是一个对象实例,该实例是一个长度为0的字符串
str = '' 放在栈内存 str = String('') 指向堆内存
JS的数据类型有哪些?哪些是引用类型?它们有什么特点?
数据类型(8种): string number boolean bigInt null undefined symbol object
值类型:string boolean number undefined null symbol(es6引入的原始数据类型,表示独一无二的值 typeof Symbol() === 'symbol' => true ; Symbol('key') !== Symbol('key') => true; Symbol('key') === Symbol('key') => false) bigInt(chrome67引入)
引用类型:object (包括:Array Function Date)
特点:1、值类型的赋值,实际就是值的拷贝
2、引用类型的赋值,实际就是地址的拷贝
什么是浅拷贝?什么是深拷贝?请用JS实现一个深拷贝
浅拷贝
var a = [1,2,3];var b = a; b[0] = 5; a = ? ([5,2,3])
因为a和b指向同一块内存地址。 当a赋值给b时, 只是将a的数据指针赋值给b,并没有开辟属于b的内存空间。
深拷贝
为b开辟独立的内存空间,并且将a的内容拷贝过来。两者互不影响。
let obj = {
name: 'kk',
bf: ['a', 1],
a() {
console.log('d')
},
offer: null
}
function deepClone(origin, target={}) {
const toStr = Object.prototype.toString,
arrStr = "[object Array]"
for(let prop in origin) {
if(origin.hasOwnProperty(prop)) {
if(origin[prop] !== null && typeof origin[prop] === 'object') {
target[prop] = toStr.call(origin[prop]) === arrStr ? [] : {}
deepClone(origin[prop], target[prop])
} else {
target[prop] = origin[prop]
}
}
}
return target
}
let ans = deepClone(obj)
ans.offer = 'xxx'
console.log(ans, obj)
a = b = 5 ; (https://blog.csdn.net/mctime/article/details/56288901)
如何判断数组类型?方法越多越好
1、Array.isArray(arr) => true
2、Object.prototype.toString.call(arr) => "[object Array]"
3、arr instanceof Array => true
4、arr.constructor == Array => true
第三和四个arr,必须在当前页面声明。如果是父页面引入iframe,也判断不出来
typeof 和 instanceof 有什么区别?
typeof用于判断变量的类型
- 数值类型 typeof 2 返回 number
- 字符串类型 typeof 'ac' 返回 string
- 布尔类型 typeof true 返回 boolean
- 对象、数组、null, 返回 object
- 函数 typeof eval 、typeof Date 返回 function
- 不存在的变量、函数或者undefined, 返回 undefined
instanceof判断某个对象是否被某个函数构造
区别:
-
typeof采用引用类型存储值时出现问题,无论引用的是什么类型的对象,都会返回object
-
es引入java的instaceof解决问题
-
instanceof与typeof相似,用于识别正在处理的对象类型。
-
不同的是,instaceof要求开发者明确地确认对象为某特定类型
== 和 === 有什么区别?
==只判断值是否相等
===判断值是否相等,类型是否相同
函数中的 arguments 是数组吗?若不是,如何将它转化为真正的数组
arguments是类数组,原型(_proto_)指向的是Object。而真正数组指向Array。
document.getElementByTagName 返回的也是类数组,原型指向HTMLCollection.
法一:Array.from(arguments)
法二:[].slice.call(arguments)
法三:扩展运算符(当数组或者对象只有一层的时候,意思是不存在数组嵌套对象,或者嵌套数组这种情况,拷贝是深拷贝。除了第一层是深拷贝,其它层都是浅拷贝) [...arguments]
法四:[].concat.apply([],arguments)
法五:Array.of(...arguments)
法六: Array(...arguments)
请说下Array的forEach、map、every、some、filter、reduce各有什么功能
forEach:遍历数组(for)
map: 返回一个数组,改变原数组的内容
every:arr.every((val, index, array)=>val=='a') 每个元素都符合条件,就返回true
some:arr.some((val, index, array)=>val=='a') 只要有一个元素符合条件,就返回true
filter: 返回一个数组,里面都是符合条件的元素。arr.filter((val, index, array)=>val=='a')
reduce: 返回一个数 可进行递归、叠加、阶乘,从左到右 (reduceRight 从右到左)
如何遍历一个对象?方法越多越好
- for(let prop in obj)
- object.keys(obj) object.values(obj)
- object.getOwnPropertyNames(obj)
以下代码结果
var fullname = 'John Doe';
var obj = {
fullname: 'Colin Ihrig',
prop: {
fullname: 'Aurelio De Rosa',
getFullname: function () {
return this.fullname;
}
}
};
console.log(obj.prop.getFullname());
var test = obj.prop.getFullname;
console.log(test());
'Aurelio De Rosa'
'John Doe' //this指向window
实现Function.prototype.bind方法, 使得以下程序最后能输出’success’
function Animal(name, color) {
this.name = name;
this.color = color;
}
Animal.prototype.say = function () {
return `I'm a ${this.color} ${this.name}`;
};
const Cat = Animal.bind(null, 'cat');
const cat = new Cat('white');
if (cat.say() === 'I\'m a white cat' && cat instanceof Cat && cat instanceof Animal) {
console.log('success');
}
Function.prototype.bind = function (obj) {
const args = [].slice.call(arguments, 1)
const that = this
const bound = function () {
const inargs = [].slice.call(arguments)
const newargs = args.concat(inargs)
const bo = obj || this
that.apply(bo, newargs)
}
function F() {}
F.prototype = that.prototype
bound.prototype = new F()
return bound
}
bind、call和apply有什么区别?
bind、call和apply的第一个参数都是this
call,传参方式是列举
apply,传参方式是数组
重点是,call,apply会指向函数,而bind只会改变this,不会指向函数
var a = {
name:"aaa",
say(type){
console.log(type,this.name);
}
}
var tn = {name:"ttt"};
var b = a.say.bind(tn);
b();//undefined ttt
var a = {
name:"aaa",
say(type){
console.log(type,this.name);
}
}
a.say("at");
var tn = {name:"ttt"};
a.say.apply(tn,["tt"])
const to = {name: 'cat', color: 'red'}
function Anmial() {
console.log(`my name is ${this.name} my color is ${this.color}`)
}
Anmial.prototype.say = function () {
console.log(`say my name is ${this.name} my color is ${this.color}`)
}
const Cat = Anmial.bind(to)
Cat() //cat red this指向to
const cat = new Cat() //undefined undefined this指向cat
cat.say() //undefined undefined this指向cat
实现bind:
Function.prototype.bind = function(obj) {
const args = [].slice.call(arguments, 1)
const that = this
const bound = function() {
const inargs = [].slice.call(argument)
const newargs = args.concat(inargs)
that.apply(obj, newargs)
}
//寄生组合式继承
function F(){}
F.prototype = that.prototype
bound.prototype = new F()
return bound
}
执行上面的方法,发现 new Cat() 能打印出属性
什么是闭包?请实现一个“有缓存功能”的加法
闭包就是能够读取其他函数内部变量的函数
let add = (()=>{
let sum = 0
return n=>sum += n
})()
console.log(add(5), add(6), add(1))
请用JS实现throttle(函数节流)函数。函数节流解释: 对函数执行增加一个控制层,保证一段时间内(可配置)内只执行一次。此函数的作用是对函数执行进行频率控制,常用于用户频繁触发但可以以更低频率响应的场景
重点: 节流,设置一次定时器后,所有触发都不能再干扰定时器,只能等它执行完才能重新设置定时器任务。
<input type="text">
<script>
// throttle 高频发生、前一个定时器结束时开启下一定时器
function throttle(fn, delay) {
let timer = null
return function() {
if(!timer) {
timer = setTimeout(function() {
fn()
timer = null
}, delay)
}
}
}
const input = document.getElementsByTagName('input')[0]
input.oninput = throttle(send, 1000)
function send() {
console.log(input.value)
}
</script>
debounce和throttle的区别?请用JS实现debounce
<script>
// debounce 重复高频发生的事件,要在最后一次才触发事件 mousemove scroll resize mousehove等
function debounce(fn, delay){
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(fn, delay)
}
}
window.addEventListener('resize', debounce(handle, 1000))
</script>
es6中的箭头函数与普通函数有什么区别?
- 普通函数this指向调用者,箭头函数的this指向定义的环境(bind\call\apply都不能改变this的指向)
- 箭头函数没有arguments,没有函数体,需要使用...rest代替
- 箭头函数不能作为构造函数,不能new
- 箭头函数不能使用yield命令,不能作为generator函数
- 箭头函数没有原型属性
- 变量提升:在js的内存机制里,function的级别最高。而箭头函数定义函数是,需要var、let、const等关键字,而只有var能进行变量提升,所以箭头函数定义一定要在调用之前
请用至少2种方法,实现Cat继承Animal的属性,并比较各方法的优缺点(https://www.jb51.net/article/163679.htm)
原型链继承---核心:将父类的实例作为子类的原型
法一:原型链继承
优点:实例是父类的实例,也是子类的实例。父类新增在原型上的方法和属性,所有子类都可以访问到。简单 易于实现
缺点:无法实现多继承。来自原型对象的引用属性和实例,所有子类都是共享的。创建子类实例时,无法向父类构造器传参。(可以在构造函数中,为实例增加实例属性。)
// 原型链继承
function Animal() {
this.species = "动物"
}
function Cat(name, color) {
this.name = name
this.color = color
}
// 核心:父类的实例作为子类的原型
Cat.prototype = new Animal()
法二:构造继承
相当于复制父类的属性方法给子类(没有用到原型),子类不能访问父类原型上的方法和属性
function Animal() {
this.species = "动物";
}
function Cat(name, color) {
this.name = name;
this.color = color;
//方法1:
Animal.call(this)
//方法2:
Anmial.apply(this)
}
法三:组合继承
function Animal() {
this.species = "动物"
}
function Cat(name, color) {
this.name = name
this.color = color
}
Cat.prototype= new Animal()
Cat.prototype.constructor = Cat //修复构造函数的指向
法四:寄生组合继承
function Animal() {
this.species = "动物"
}
function Cat(name, color) {
Animal.call(this)
this.name = name
this.color = color
}
function F(){}
F.prototype= Animal.prototype
Cat.prototype= new F()
Cat.prototype.contructor = Cat
什么是原型对象?什么是原型链?(https://blog.csdn.net/xiaoermingn/article/details/80745117)
原型对象:
- 所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
- 所有函数都有一个prototype(原型)属性,属性值是一个普通的对象
- 所有引用类型的__proto__属性都指向它构造函数的prototype
var a = [1,2,3] a.__proto__ === Array.prototype => true
原型链:
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即是构造函数的prototype,如果还没有找到就会在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们成为原型链。
- 一直往上查找,直到到null还没有找到,则返回undefined
- Object.prototype.__proto__ === null
- 所有从原型或更高级原型中得到、执行的方法,其中的this在执行时,执行当前这个触发事件的执行对象。
JS的最顶层对象是什么?它的原型对象是什么?
Object Object.prototype
什么是构造函数?什么是实例?
构造函数:带有初始化变量(私有、公共),带有一系列操作方法(私有、公共)的一个函数(也叫方法,参数可有可无)
实例:使用构造函数和new关键字创建的就是实例
如何通过一个实例访问它的构造函数及原型对象?
注:通过class定义的类 和通过构造函数定义的类 二者本质相同。并且在js执行时,会将第一种转会为第二种执行。所以 ES6 class的写法实质就是构造函数。
实例访问构造函数:example.constructor
实例访问原型对象:example.__proto__
new一个实例,经历了什么过程?
function Parent(name, age) {
//1、创建一个新对象,赋予this,这一步是隐性的
// let this = {}
// 2、给this指向对象赋予构造属性
this.name = name
this.age = age
// 3、如果没有手动返回对象,则默认返回this指向的这个对象,也是隐性的
// return this
}
自己写一个new方法
function Parent(name, age) {
this.name = name
this.age = age
}
Parent.prototype.sayName = function() {
console.log(this.name)
}
function newMethod(Parent, ...rest) {
let child = Object.create(Parent.prototype)
let result = Parent.apply(child, rest)
return typeof result === 'object' ? result : child
}
const child = newMethod(Parent, 'miki', 18)
child.sayName() //miki
child instanceof Parent //true
child.hasOwnProperty('name') //true
child.hasOwnProperty('age') //true
child.hasOwnProperty('sayName') //false
es6中的static的作用是什么?用es5如何实现?
类就是实例的原型。class就是构造函数。在方法前加上static关键字,该方法不会被实例继承,只能直接通过类来调用,也就是静态方法。
class Parent {
static say() {
return 'aaa'
}
}
let child = new Parent()
// consol.log(child.say())
console.log(Parent.say())
父类静态方法,子类如何继承?
class Parent {
static say(){
console.log('aaa')
}
}
Parent.say()
class Child extends Parent {}
Child.say()
用es5如何实现?
//构造继承
function Parent() {}
Parent.say = function () {
console.log('aaa') //静态方法
}
Parent.say()
function Child() {
return Object.create(Parent)
}
let child = new Child()
child.say()
以最小的改动解决以下代码的错误(可以使用es6)
const obj = {
name: " jsCoder",
skill: ["es6", "react", "angular"],
say: function () {
for (var i = 0, len = this.skill.length; i < len; i++) {
setTimeout({
console.log('No.' + i + this.name);
console.log(this.skill[i]);
console.log('--------------------------');
}, 0);
console.log(i);
}
}
};
obj.say();
/*
期望得到下面的结果:
1
2
3
No.1 jsCoder
es6
--------------------------
No.2 jsCoder
react
--------------------------
No.3 jsCoder
angular
--------------------------
*/
answer1
const obj = {
name: " jsCoder",
skill: ["es6", "react", "angular"],
say: function () {
for (let i = 1, len = this.skill.length + 1; i < len; i++) {
setTimeout(() => {
console.log('No.' + i + this.name)
console.log(this.skill[i - 1])
console.log('--------------------------')
}, 0)
console.log(i)
}
}
};
obj.say();
如果不修改,会打印出什么结果?
0
1
2
'No.3'
Uncaught TypeError: Cannot read property '3' of undefined
'No.3'
Uncaught TypeError: Cannot read property '3' of undefined
'No.3'
'undefined'
Uncaught TypeError: Cannot read property '3' of undefined
let、const、var有什么区别?
- 什么时候提出? var是es5提出的,let、const是es6提出的
- 变量提升 var能够进行变量提升,let、const不可以
- 暂时性死区(TDZ) 在块级作用于内,let、const具有暂时死区,一进入作用域内,变量就已经存在,但是在变量声明前不能被访问或者调用,否则会报错 ReferenceError
- 重复声明 var可以重复声明 let、const不可以
- 是否存在块级作用域 var不存在(1、内部变量可能会覆盖外部变量2、用于计数的循环变量泄露为全局变量),let、const存在
- 声明的变量能否被修改 var和let可以 const不可以,一旦声明必须立即初始化
什么是函数作用域?还有什么其他作用域?如何工作的?
什么是作用域:浏览器给js的生存环境叫作用域。
函数作用域 全局作用域 块级作用域
函数作用域:
- 在函数调用的时候,会创建函数作用域。调用完会摧毁
- 每一次调用函数都会创建一个函数作用域,并且相互独立
- 函数作用域可以访问全局作用域的变量,全局作用域不可以访问函数作用域的变量
- 函数内访问变量,会从自身函数作用域内找,找不到就往上一级作用域找,直到找到全局作用域。如果还是找不到,就返回ReferenceError
- 在函数作用域内要访问全局变量,可以使用window
请说出以下代码打印的结果
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
JS单线程是怎么运作的?请说下“异步”和“同步”的区别(https://blog.csdn.net/qq_39039128/article/details/104734337)
js单线程要分为浏览器和node环境
浏览器环境:1、先执行同步代码 2、执行异步代码的所有微任务 3、执行异步代码的一个宏任务 4、执行执行异步代码的所有微任务5、执行异步代码的一个宏任务 循环
node环境1、先执行同步代码 2、执行异步代码的所有微任务 3、执行异步代码的所有宏任务 4、执行执行异步代码的所有微任务5、执行异步代码的所有宏任务 循环
node的宏任务有六个任务队列
浏览器的宏任务都在同一个任务队列
处理异步任务的方法有哪些?
- 回调函数 (回调不一定是异步 使用这个实现)
function f1(callback) { setTimeout(function() { callback() }, 1000) } function f2(){} f1(f2)
- 事件监听(事件驱动模式)on,bind,listen,addEventListener,observe
易于理解、可绑定多个事件,可以去耦合,利于模块化。-----整个程序变成事件驱动型,运行流程不清晰。function f1() { setTimeout(function(){ f1.trigger('done') },1000) } f1.on('done',f2)
- 发布/订阅(publish/subscribe)
JQuery.subscribe('done', f2) //订阅done function f1() { setTimeout(function() { jQuery.publish('done') //执行f1任务代码 }, 1000) } JQuery.unsubscribe('done', f2) //执行后取消订阅
-
Promise对象
-
async/await
如何将一个普通异步函数封装为Promise?
普通异步函数
var showMsg = function (callback) {
setTimeout(function () {
alert('hello');
// 此处添加回调
callback();
}, 5000);
};
Promise
var showMsg = function () {
return new Promise((resolve, reject) => {
setTimeout(function () {
console.log('hello')
resolve(123)
}, 5000)
})
}
function callback(str) {
console.log(`i\'m ${str}`)
}
showMsg().then(function (str) {
callback(str)
})
请实现一个同步的delay方法
function delay(time) {
const start = (new Date()).getTime()
while ((new Date()).getTime() - start < time) {
continue
}
}