前端开发必会的JavaScript硬知识

参考: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判断某个对象是否被某个函数构造

区别:

  1. typeof采用引用类型存储值时出现问题,无论引用的是什么类型的对象,都会返回object

  2. es引入java的instaceof解决问题

  3. instanceof与typeof相似,用于识别正在处理的对象类型。

  4. 不同的是,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中的箭头函数与普通函数有什么区别?

 

  1. 普通函数this指向调用者,箭头函数的this指向定义的环境(bind\call\apply都不能改变this的指向)
  2. 箭头函数没有arguments,没有函数体,需要使用...rest代替
  3. 箭头函数不能作为构造函数,不能new
  4. 箭头函数不能使用yield命令,不能作为generator函数
  5. 箭头函数没有原型属性
  6. 变量提升:在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)

原型对象:

  1. 所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
  2. 所有函数都有一个prototype(原型)属性,属性值是一个普通的对象
  3. 所有引用类型的__proto__属性都指向它构造函数的prototype

var a = [1,2,3] a.__proto__ === Array.prototype => true

原型链:

当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即是构造函数的prototype,如果还没有找到就会在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们成为原型链

  1. 一直往上查找,直到到null还没有找到,则返回undefined
  2. Object.prototype.__proto__ === null
  3. 所有从原型或更高级原型中得到、执行的方法,其中的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的宏任务有六个任务队列

浏览器的宏任务都在同一个任务队列

处理异步任务的方法有哪些?

  1. 回调函数 (回调不一定是异步 使用这个实现)
    function f1(callback) {
        setTimeout(function() {
            callback()
        }, 1000)
    }
    function f2(){}
    f1(f2)

     

  2. 事件监听(事件驱动模式)on,bind,listen,addEventListener,observe
    function f1() {
        setTimeout(function(){
            f1.trigger('done')
        },1000)
    }
    
    f1.on('done',f2)
    
    易于理解、可绑定多个事件,可以去耦合,利于模块化。-----整个程序变成事件驱动型,运行流程不清晰。
     
  3. 发布/订阅(publish/subscribe)
    
    JQuery.subscribe('done', f2) //订阅done
    function f1() {
        setTimeout(function() {
            jQuery.publish('done')  //执行f1任务代码
        }, 1000)
    }
    
    JQuery.unsubscribe('done', f2) //执行后取消订阅
    

     

  4.  Promise对象

  5.  

    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
    }
}

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值