2022前端面经--JS

1、JS new 生成对象的过程

1、创建空对象
2、链接到原型(将空对象的原型指向构造函数的原型)
3、绑定 this(将空对象作为构造函数的上下文)
4、返回新对象(对构造函数有返回值的处理判断)

  // function fun(age, name) {
        //     this.name = name;
        //     this.age = age;
        // }
        // //new关键字实例化对象
        // console.log(new fun(20, "gj"))

        function Fu(age, name) {
            this.name = name;
            this.age = age;
        }

        function creat(fn, ...args) {
            let obj={};//创建新对象

            obj.__proto__==fn.prototype;//将空对象的原型指向构造函数的原型

            var result=fn.apply(obj,args);//改变this指向

            return result instanceof Object? result :obj;//对构造函数返回值的处理判断(当构造函数有返回值且是一个引用类型时,要返回指定的引用类型,当不是引用类型时,才返回新对象obj)

            console.log(args);
        }

        console.log(creat(Fu,20,'gj'))

2、js 延迟加载方式?

defer:等html加载完,才会执行js脚本,顺序执行js脚本

async:async和html解析同步,不是顺次执行js脚本,(谁先加载完谁先执行)

3、js数据类型

基本类型:Boolean, String, Number, Null, Undefined, Symble, BigIn 
引用类型:Object(Array, Function, RegExp, Date)
区别:
1. 存储位置不同--栈和堆
原始类型占据的空间是固定的,存储在较小的内存区域-栈中,便于快速查找变量值;
引用类型存储在堆中,该对象在堆中的地址存放在栈中

2. 赋值方式
原始值赋值:拷贝值的副本给新的变量,两个变量相互独立,改变其中一个值不会影响另外一个;
引用值赋值:将引用传递给新的变量,实际上是将地址引用赋值给新的变量,两个变量有相同的地址,指向堆中的同一块区域,改变其中一个的值,另外一个随之改变

3. 比较方式
原始值是值的比较,引用值是引用(变量地址)的比较

3、null 和undefined区别

1.最初作者设计js是先设计的null(参考Java语言)

2.null会被隐式转换为0,很不容易发现错误

3.undefined是为了弥补null的不足

null表示一个无的对象,转换为数值是为0,undefined表示一个无的原始值,转换为数值时是NaN

null 表示没有对象,即该出不该有值

典型用法:

  • 作为函数的参数,表示该函数的参数不是对象
  • 作为对象原型链的终点

undefined 表示缺少值,就是此处应该有一个值,但是还没有定义

典型用法:

  • 变量被声明了,但没有赋值时,就等于undefined
  • 调用函数时,应该提供的参数没有提供时,该参数等于undefined
  • 对象没有赋值的属性,该属性的值为undefined
  • 函数没有返回值时,默认返回undefined

详见: undefined和null的区别-阮一峰

4、== 和===

== :比较值

string==number  ||boolean==number...都会隐式转换
通过valueof转换

===:除了值,还比较数据类型

5、js微任务和宏任务

1.js是单线程的语言
2.js代码执行流程:同步执行完==》事件循环
	同步执行完了才会执行事件循环的内容
	进入事件循环:网络请求,定时器,事件...
3.事件循环中包括:【宏任务,微任务】
	微任务:promise.then
	宏任务:setTimeout,setInterval..
执行宏任务的前提是执行完了所有的微任务

流程:同步事件==》事件循环【微任务和宏任务】==》微任务==》宏任务
 console.log('start')
        setTimeout(() => {
            console.log('setTimeout')
        }, 0)

        new Promise((resolve) => {
            console.log('promise')
            resolve()
        })
            .then(() => {
                console.log('then1')
            })
            .then(() => {
                console.log('then2')
            })

        console.log('end')
        
//start,promise,then1,then2,setTimeout

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
//整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。

6、变量定义提升、this指针指向、运算符优先级

参考链接:https://www.cnblogs.com/xxcanghai/p/5189353.html

经典面试题: 
function Foo() {
            getName = function () { console.log("1"); };
            return this;
        }

        Foo.getName = function () { console.log("2"); };
        Foo.prototype.getName = function () { console.log("3"); };
        var getName = function () { console.log("4"); };
        function getName() { console.log("5"); }

        //请写出以下输出结果:
        Foo.getName();  //访问Foo函数上存储的静态属性,2
        getName();      //直接调用 getName 函数(变量提升,5函数声明和4函数表达式)4
        Foo().getName();//执行Foo函数,调用Foo函数的getName属性函数  1
        getName();  //1    
        new Foo.getName();//相当于new (Foo.getName)();  2
        new Foo().getName();//想当于(new Foo()).getName()  3
        new new Foo().getName();//new ((new Foo()).getName)();初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new。  3

7、call,apply,bind区别

参考:关于this指向的问题

可改变函数内部的this指向

语法:函数.call()  函数.apply()  函数.bind()

1.call,apply 可以立即执行,bind返回的是函数,需要+()执行
2.参数不同,apply第二个参数是数组,call和bind有多个参数(apply是将参数并成一个数组,call和bind是将参数依次列出。)


Function.prototype.myCall = function(context) { // foo.myCall(obj)
    var context = context || window;
    context.fn = this; //this就是函数fn

    var args = [...arguments].slice(1);
    var result = context.fn(...args);
    
    delete context.fn;

    return result;
}

Function.prototype.myApply = function(context){
    var context = context || window;
    context.fn = this;

    var _args = [...arguments].slice(1);

    var result = context.fn(_args);//绑定上下文,这不就是绑定上下文吗

    delete context.fn;

    return result;
}

Function.prototype.myBind = function(context) {
    var context = context || window;
    context.fn = this; //this就是函数fn

    var args = [...arguments].slice(1);
    return function() {
        var result = context.fn(...args);

        delete context.fn;
        return result;
    }
}

function foo() {
    console.log(this.name)
}

var obj = {
    name: 'alice',
    age: 17
}

foo();
foo.myCall(obj)
foo.myApply(obj)
foo.myBind(obj)()

> "jenny"
> "alice"
> "alice"
> "alice"
- apply 方法传入两个参数:一个是作为上下文的对象,另一个是作为函数所组成的数组

```javascript
var obj = {
    name : 'sss'
}

function func(firstName, lastName){
    console.log(firstName + ' ' + this.name + ' ' + lastName);
}

func.apply(obj, ['A', 'B']);    // A sss B
```

- call 方法第一个参数也是作为作为函数上下文的对象,但是后面传入的是一个参数列表,而不是单个数组

```javascript
var obj = {
    name: 'sss'
}

function func(firstName, lastName) {
    console.log(firstName + ' ' + this.name + ' ' + lastName);
}

func.call(obj, 'C', 'D');       // C sss D
```

- bing接受的参数有两个部分,第一个参数是作为函数上下文的对象,第二部分参数是一个列表,可以接受多个参数

```javascript
var obj = {
    name: 'sss'
}

function func() {
    console.log(this.name);
}

var func1 = func.bind(null, 'xixi');
func1();
```

apply、call 方法都会使函数立即执行,因此它们也可以用来调用函数

bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 func 中的 this 并没有被改变,依旧指向全局对象 window

bind 在传递参数的时候会将自己带过去的参数排在原函数参数之前

```javascript
function func(a, b, c) {
    console.log(a, b, c);
}
var func1 = func.bind(this, 'xixi');
func1(1,2) // xixi 1 2
```

8、深拷贝和浅拷贝

浅拷贝:只复制引用,未复制真正的值

 //浅拷贝,互相影响
         var arr1=['a','b','c'];
         var arr2=arr1;

         arr1[0]='66666';

         console.log(arr1);
         console.log(arr2);
         
         方法2:Object.assign()
       
       
  //深拷贝
        var obj3 = {
            a: 1,
            b: 2
        }
        var obj4 = JSON.parse(JSON.stringify(obj3));//JSON.parse方法
        obj3.a=8888;
        obj4.b=9999;
        console.log(obj3, obj4);
  

9、如何正确判断this指向

  • 1)通过 new 创建的实例对象,this 绑定到新创建的对象
  • 2)call bind apply,绑定到指定的对象
  • 3)上下文调用,绑定到上下文对象上
  • 4)都不是,则为默认绑定。严格模式下,绑定到 undefined ,否则绑定到全局对象
  • 5)箭头函数根据外层函数的作用域来决定 this (箭头函数定义时所在的作用域)

11.es6新特性

let、const、var的区别

let 和 const 都是 ES6 新增的声明方式,分别用于声明一个变量和一个常量

let V.S. var:

  • let不存在变量提升
  • let存在暂时性死区 (let命令声明之前,该变量不可用)
  • let不允许重复声明
  • let在声明的块作用域内有效
const用于声明一个只读常量,一旦声明,该值无法改变,要求在声明的时候赋值;与let一样,不存在变量提升、存在暂时性死区、块级作用域、不允许重复声明;

const声明的常量无法改变,是指变量指向的地址无法改变,对于简单类型(数值、字符串、布尔)来讲,值就保存在变量指向的那个地址,等同于常量;对于符合类型(对象、数组),const只能保证指针固定,至于指针指向的数据结构,就无法控制了
 // function test() {
        //     for (var i = 0; i < 5; i++) {
        //         console.log("var:" + i)
        //     }
        // }
        // test();//0,1,2,3,4
        // console.log(i);

        // for (let j = 0; j < 5; j++) {
        //     console.log("let:" + j)
        // }
        // console.log(j);//0,1,2,3,4


        // for (var i = 0; i < 5; i++) {
        //     setTimeout(() =>
        //         console.log(i), 0)//5,5,5,5,5
        // }


        for (let i = 0; i < 5; i++) {
            setTimeout(() =>
                console.log(i),10)//0,1,2,3,4
        }
变量的解构赋值

ES6中按照一定模式,从数组和对象中提取值,为变量进行赋值的过程为解构赋值

包括数组、对象、字符串、数值和布尔值、函数参数等的解构赋值

let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let { bar, foo } = { foo: "aaa", bar: "bbb" }; 
const [a, b, c, d, e] = 'hello';
解构赋值的应用:

交换变量值

[x,y] = [y, x]

从函数返回多个值

function example(){
    return [1,2,3]
}

[a, b, c] = example() 

用于函数参数,将变量名与函数参数对应起来

function f([x, y, z]){
    // 函数体
    ...
} 

f({x: 1, z: 3, y: 2}) // 参数无序

指定参数的默认值

// 参数 b 指定默认值 1
function foo(a, b = 1) {
    return a * b;
}

console.log(foo(3, 2))
console.log(foo(3))

提取json数据

let jsonData = {name: 'alice', age: 23, data: [12, 45]}
let {name, age, data: number} = jsonData
console.log(name, age, number) // "alice" 23 Array [12, 45]

遍历 Map 结构

let map = new Map();
map.set('name', '翠芝')
map.set('city', '上海')
map.set('lover', '世均')

for(let [key, value] of map) {
    console.log(`key=${key}, value=${value}`)
}

> "key=name, value=翠芝"
> "key=city, value=上海"
> "key=lover, value=世均"

// 获取键名
for(let [key] of map) {
    // ...
}

// 获取键值
for(let [value] of map) {
    // ...
}

模块按需导入

const {SourceMapConsumer, SourceNode} = require("source-map")
箭头函数及其this
(x, y) => {return x+y}
  • 只有一个参数时,圆括号可省略;
  • 只有一个语句表达式时,花括号可省略
  1. 箭头函数中的 this 是函数定义时所在的对象,而不是函数运行时所在的对象
  2. 不可以作为构造函数,因为箭头函数的原型为 undefined
var foo = (name, age) => {
    let obj = {};
    obj.name = name, 
    obj.age = age
    return obj
}

var p = new foo('lucy', 12) // Uncaught TypeError: foo is not a constructor

foo.prototype === undefined // true

不可以使用 arguments 对象,可以用 rest 参数代替

const sum = (...args) => {
    let total = 0;
    args.forEach(item => {
        total += item
    })
    return total;
}

res = sum(1,2,3)
console.log(res) // 6
Symbol概念及作用

Symbol是ES6新增的基础数据类型,不能用new来实例化

每个Symbol实例都是唯一的,比较两个Symbol实例的时候,总会返回false
Set和Map数据结构

ES6新增数据结构Set,它类似于数组,但成员的值唯

const s = new Set();
s.add(1);
s.add(2);
s.add(3);
for(let ele of s) {
    console.log(ele)
} // 1 2 3 
s.delete(2)

ES6提供了Map数据结构,是键值对的集合,各种类型的值都可以作为键

Proxy

Proxy用于修改某些操作的默认行为,可以理解为,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截.

Vue3.0中采用Proxy代替Vue2.0中的Object.defineProperty进行数据劫持

var obj = new Proxy({}, {
    get: function(target, property) {
        if (property in target) {
            return target[property];
        } else {
            throw new ReferenceError(`Property ${property} doesn't exist!`)
        }
    },
    set: function(obj, prop, value) {
        if (prop === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError('not an integer')
            }
            if (value > 200){
                throw new RangeError('invalid input')
            }
            obj[prop] = value;
        }
        obj[prop] = value; // 挂载其它属性
    }
})

obj.age = 100;
obj.name = 'alice'
for(let key in obj) {
    if (obj.hasOwnProperty(key)) {
        console.log(key,'=',obj[key])
    }
}

age = 100
name = alice
Promise A+规范

Promise是JS中的异步编程方案,主要用于解决回调地狱问题

  • Promise是一个构造函数,new Promise返回一个promise对象,构造函数接收一个executor执行函数作为参数,executor有两个形参resolve, reject;
  • promise有三种状态: pending, fulfilled, rejected,
    • 只能由pending=>fulfilled/rejected,一旦修改就不能改变
循环语法比较以及使用场景(for, forEach, for…in, for…of)

for 按顺序遍历,遍历的对象有 length 属性

var arr = [233, 666, 2333]

for(var index = 0; index < arr.length; index++) {
    console.log(arr[index])
}

forEach: for循环的缺陷在于写法比较麻烦,因此数组提供了内置的 forEach 方法

arr.forEach((val, index) => {
    console.log(index,val)
})

0 233
1 666
2 2333

forEach循环的缺陷是无法中途跳出循环,break或return都不能奏效

for…in 语句主要是为遍历对象而设计的,可以用来遍历对象的所有属性名,也会遍历出原型链上的成员属性;属性名出现的顺序是不确定的

for…in 循环缺点:

  • 1.数组的键名是数字,但是 for…in 循环是以字符串作为键名"0", “1”, "2"等
  • 2.for…in循环不仅遍历数字键名,还会遍历手动添加的其它键,包括原型链上的键
  • 3.某些情况下,for…in循环会以任意顺序遍历键名

for…of

 ES6引入了for...of循环,作为遍历所有数据结构的统一的方法;
  
  一个数据结构部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for...of 循环遍历成员
  
  for...of 循环适用范围:数组、Set和Map结构、类数组对象、字符串等;
复制代码

特点:

 > for...of语法简洁,但没有for...in的缺点;
  
  > 不同于forEach,for...of可以与break, continue, return配合使用;
  
  > 提供了遍历所有数据结构的统一操作接口;
复制代码
Proxy

Proxy用于修改某些操作的默认行为,可以理解为,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截.

Vue3.0中采用Proxy代替Vue2.0中的Object.defineProperty进行数据劫持

var obj = new Proxy({}, {
    get: function(target, property) {
        if (property in target) {
            return target[property];
        } else {
            throw new ReferenceError(`Property ${property} doesn't exist!`)
        }
    },
    set: function(obj, prop, value) {
        if (prop === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError('not an integer')
            }
            if (value > 200){
                throw new RangeError('invalid input')
            }
            obj[prop] = value;
        }
        obj[prop] = value; // 挂载其它属性
    }
})

obj.age = 100;
obj.name = 'alice'
for(let key in obj) {
    if (obj.hasOwnProperty(key)) {
        console.log(key,'=',obj[key])
    }
}

age = 100
name = alice
复制代码
Generator及其异步方面的应用
function * test(a, b){
    var cur = 1;
    cur += a;
    yield cur;
    cur += b;
    yield cur;
    return 'ok';
}

var gen = test(2, 3);
gen.next(); // {value: 3, done: false}
gen.next(); // {value: 6, done: false}
gen.next(); // {value: "ok", done: true}
复制代码
async和await

async函数就是Generator函数的语法糖

  • 1.async函数返回一个promise对象,async函数内部 return 语句返回的值,会成为 then 方法回调函数的参数
async function test(){
    return 'hello, world'
}

test().then(val => console.log(val)) 

hello, world
复制代码
  • 2.async函数内部抛出错误,会导致返回返回的Promise对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到
async function test(){
    throw new Error('something went wrong')
}

test().then(
    val => console.log(val),
    error => console.log(error)
)

Error: something went wrong
复制代码
  • 3.async函数内部的异步操作执行完,才会执行 then 方法指定的回调函数
async function getText(){
    return 'text';
}

async function getData(){
    return 'data';
}

async function test() {
    let res1 = await getText();
    let res2 = await getData();
    return [res1, res2];
}

test().then(val => console.log(val))

["text", "data"]
复制代码

test中的两个操作:getText, getData都完成之后,才会执行then方法里面的 console.log

  • 4.只要一个 await 语句后面的Promise变为 reject,那么整个 async 函数都会中断执行;如果希望前面的异步操作失败不会影响后续,那么可以将await放在try…catch结构里面,这样后续的await总会执行;或者await后面的Promise对象再跟一个 catch 方法,处理可能出现的错误
async function f() {
    try {
        await Promise.reject('something went wrong')
    } catch(e) {} // 去掉catch,后续的await语句不会执行
    return await Promise.resolve('hello, world')
}

f()
.then(v => console.log(v))

hello, world

或

async function f() {
    await Promise.reject('something went wrong')
    .catch(e => console.log(e));
    return await Promise.resolve('hello, world')
}

f()
.then(v => console.log(v))

something went wrong
hello, world
复制代码

async函数注意事项:

  • 1.async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里
  • 2.await命令后面的 Promise 对象,最好加入错误处理机制
  • 3.多个 await 命令后面的异步操作,如果不存在继发关系,最好同时触发,减少等待时间(await Promise.all([getRes1(), getRes2()])
  • 4.await命令只能用在 async 函数之中,如果用在普通函数中,就会报错
class基本语法及继承
class Point{
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    static hello(){
        console.log('hello, world!!!')
    }

    toString(){
        return `${this.x}, ${this.y}`
    }
}

class ColorPoint extends Point{
    constructor(x, y, color) {
        //1. 子类必须在constructor方法中调用 super 方法,否则创建实例时就会报错
        //是因为子类没有自己的 this 对象,而是继承父类的 this 对象

        //2. 子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字
        //是因为子类实例的构建,是基于对父类实例进行加工,只有 super 方法才能返回父类实例
        super(x, y);
        this.color = color;
    }

    toString(){
        return `${this.color}, ${super.toString()}`;
    }
}

var cp = new ColorPoint(14, 16, 'red');
console.log(cp)
ColorPoint.hello()

复制代码
ES6模块加载和CommonJS加载的原理、区别

CommonJS: require引入, module.exports = {}导出 1.基本数据类型,属于复制,在另一个模块中改变值,原文件不受影响

2.复杂数据类型,属于浅拷贝,在另一个模块中改变值,原文件会响应改变

3.使用require加载某个模块时,就会运行整个模块

4.只需要require一次,因为模块内容会被缓存;若要重新加载,需要手动清除系统缓存

5.循环加载时,属于加载时执行,模块一旦被require就会全部执行;a.js与b.js循环引用,运行a.js,脚本执行到require(‘./b.js’)时就会执行b.js,如果a还未导出,b.js中对a的引用全部为undefined

ES6 Module: import引入 1.对于只读基本数据类型,不允许修改

2.对于动态来说,原始值发生变化,import加载的值也会变化,不论是基本数据类型还是复杂类型

3.循环加载时,ES6模块是动态引用,存在变量提升,函数会被提升到顶部

12.Cookie、localStorage、SessionStorage…

cookie:

数据生命性:一般由服务器生成,可设置失效时间。(也可以由客户端生成)

存放数据大小: 一般大小不能超过4KB

作用域:Cookie的作用域仅仅由domain和path决定,与协议和端口无关

与服务器端通信:浏览器每次向服务器发出请求,就会自动把当前域名下所有未过期的Cookie一同发送到服务器(会带来额外的性能开销)

易用性:缺乏数据操作接口(document.cookie)。

适用场景:只有那些每次请求都需要让服务器知道的信息(保持用户的登录状态),才应该放在 Cookie 里面。

localStorage:

数据生命性:存储在 localStorage 的数据可以长期保留;

存放数据大小: 一般为5MB

作用域:存储在 localStorage 中的数据特定于页面的协议。也就是说 http://example.com 与 https://example.com 的 localStorage 相互隔离。

与服务器端通信:不会自动把数据发给服务器,仅在本地保存。

易用性:有很多易用的数据操作接口,比如setItem、getItem、removeItem

适用场景:常用于长期登录(+判断用户是否已登录),适合长期保存在本地的数据

SessionStorage:

数据生命性:sessionStorage 里面的数据在页面会话结束(关闭对应浏览器标签或窗口)时会被清除。

存放数据大小: 一般为5MB

作用域:存储在 sessionStorage 中的数据特定于页面的协议。也就是说 http://example.com 与 https://example.com 的 sessionStorage 相互隔离。

与服务器端通信:不会自动把数据发给服务器,仅在本地保存。

易用性:有很多易用的数据操作接口,比如setItem、getItem、removeItem

适用场景:敏感账号一次性登录;

WebStorage

存储空间更大;

更节省流量(没有额外性能开销)

获取数据从本地获取会比服务器端获取快得多,所以显示更快

IndexedDB

IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。

IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。

IndexedDB储存空间大,一般来说不少于 250MB,甚至没有上限。

IndexedDB不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。

提供查找接口,还能建立索引

异步操作:IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。

13.闭包

闭包是指有权访问另外一个函数作用域中的局部变量的函数

js特点父对象的所有变量,对子对象都是可见的,反之则不成立。

var age = 10;
function foo(){
    console.log(age);//  10
	var name = "gj";
	return function(){
		console.log(name);
	}
}
console.log(name);//  未定义
var bar = foo();
bar();//gj


  1. 让外部访问函数内部变量成为可能
  2. 可以避免使用全局变量,防止全局变量污染
  3. 局部变量会常驻在内存中
  4. 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)

14.原型,原型链

(借用网络热图)

每一个javascript对象(除null外)创建的时候,都会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。

var obj = new Object();

img

14.1 prototype(函数独有)(显示原型)

在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象(函数也是对象)

prototype属性也叫原型属性,它是函数独有的,每个函数都有一个prototype属性,它是一个指针,指向一个对象,这个对象包含了所有实例共享的属性和方法.

img

function Animal(weight) {
   this.weight = weight
 }

img

例如:cat1和cagt2实例化了Animal,在cat1和cagt2本身是没有hieght属性的,但是能打印出height的值均为10,其实是在cat1和cagt2继承了原型Animal.prototype中的height属性

 function Animal(weight) {
    this.weight = weight
  }
  Animal.prototype.height = 10
  var cat1 = new Animal()
  var cat2 = new Animal()
  console.log('cat1',cat1.height)//10
  console.log('cat2',cat2.height)//10
14.2 _proto(隐式原型)

这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。

  function Animal(weight) {
     this.weight = weight
  }
  Animal.prototype.height = 10
  var cat1 = new Animal()
  var cat2 = new Animal()
 console.log('cat1.__proto__ === Animal.prototype',cat1.__proto__ === Animal.prototype)
 console.log('cat2.__proto__ === Animal.prototype',cat2.__proto__ === Animal.prototype)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Er64H5dr-1663508245687)(C:\Users\34818\AppData\Roaming\Typora\typora-user-images\image-20220512210738205.png)]

__proto__是实例指向原型的属性

prototype是对象或者构造函数指向原型的属性

img

14.3 prototype与proto的区别
  • prototype是函数独有的,而__proto__是每个对象都会拥有的(包括函数)

  • prototype的作用是保存所有实例公共的属性和方法;__proto__的作用是当访问一个对象的属性时,如果内部没有该属性,就会在它的__proto__属性所指的那个父对象去找,父对象没有,再去父对象的父对象里找…直到null,即原型链.

14.4 constructor

每个原型都有一个constructor属性,指向该关联的构造函数。

img

14.5 原型链

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。这样就形成了原型链

function Animal(weight) {
   this.weight = weight
}
 Animal.prototype.name = 'animal'
 var cat1 = new Animal()
 cat1.name = 'littleCat'
 console.log('cat1.name',cat1.name)
 delete cat1.name;
 console.log('cat1.name',cat1.name)

可以看见,删除属性前,那么是littleCat,删除那么属性后,该实例没有了name属性,找不到name属性的时候,它就会去 它的对象原型中去找也就是去cat1.__proto__中也就是Animal.prototype中去寻找,而Animal.prototype中的name属性的值是animal,所以删除name属性后的值变成了原型中属性name的值animal。

14.6 原型的原型

我们说原型是对象创建的时候关联的另一个对象,那么原型也是一个对象,既然是对象那么原型也应该关联一个对象是原型的原型

那么原型对象创建的时候也会关联一个对象

img

Object.prototype.proto 的值为 null 即 Object.prototype 没有原型,所以可以想象在原型链中,当属性找到顶层原型都没有属性那就是没有这个属性.

img

将原型的实例赋值给另一个对象,另一个对象再赋值给其他的对象,在实际的代码中对对象不同的赋值,就会形成一条原型链

 function Animal(weight) {
     this.weight = weight
 }
 Animal.prototype.name = 'animal'
 var cat1 = new Animal()
 var pinkCat = cat1
 console.log('pinkCat.name',pinkCat.name)
 console.log('pinkCat.__proto__ === cat1.__proto__ == Animal.prototype',pinkCat.__proto__ === cat1.__proto__ == Animal.prototype)
 var samllPinkCat = pinkCat
 console.log('samllPinkCat.name',samllPinkCat.name)
 console.log(samllPinkCat.__proto__ == pinkCat.__proto__ === cat1.__proto__ == Animal.prototype)

以上cat1实例化了Animal,cat1赋值给了pinkCat,pinkCat又赋值给了samllPinkCat,就形成看原型链,从samllPinkCat,pinkCat到cat1最后到Animal。

每个对象拥有一个原型对象,通过 proto 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.proptotype.proto指向的是null)。这种关系被称为原型链(prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法

15、判断空对象

  //使用json自带的stringify方法来判断
        let obj={};
        console.log(obj);
        console.log(JSON.stringify(obj));
        if(JSON.stringify(obj)==='{}'){
            console.log("是空对象")
        }

        //使用Object.key()来判断
        var obj1={};
        console.log(Object.keys(obj1));
        if(Object.keys(obj1).length===0){
            console.log("是空对象")
        }

16、promise

 //promise常规写法
 const p =new Promise((resolve, reject) => {
            resolve();
        }).then((value) => {
            console.log("成功时调用" + value)
        }).catch((reason) => {
            console.log("失败时调用" + reason)
        })
 
 
 //catch
  const p = new Promise((resolve, reject) => {
            // resolve();
            // reject();
            console.log(a);
        })

        //1.promise状态为reject时执行
        //2.promse执行体中出现错误代码时
        p.catch((reason)=>{
            console.log("catch 错误")
        })
        console.log(p)


//
  var p = new Promise((resolve, reject) => {
            resolve('成功的结果');//调用函数,状态变为fulfilled(成功状态)
            // reject('失败的结果');//调用函数,状态变为reject(拒绝状态)

        })

        const t = p.then((value) => {
            console.log("调用成功  " + value)

            // console.log(a) //报错会变为reject状态
            // return 222  //改变状态为fuifilled
        }, (reason) => {
            console.log(" 失败   " + reason)
            // return  11111 
        })

        const t2 = t.then((value) => {
            console.log("2次调用成功  " + value)
        }, (reason) => {
            console.log("2次调用失败" + reason)
        })

17.多维数组最大值


        function fnArr(arr) {
            var newArr = [];
            arr.forEach((item, index) => {
                newArr.push(Math.max(...item))
            });
            return newArr;
        }

        console.log(fnArr([
        [4, 5, 1, 3], 
        [13, 27, 18, 26],
        [32, 35, 37, 39]]))

17、数组去重

1.ES6提供的数据结构Set
function unique (arr) {
  return Array.from(new Set(arr))
}
//去重代码少。但是无法去除{}空对象

function newArrFn (arr) {
      // .new Set方法,返回是一个类数组,需要结合 ...运算符,转成真实数组
    //:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
   return ([...new Set(arr)])
  }


2.双层for循环+splice
思路:遍历数组拿当前项与后边所有项对比,若相同删除重复项
function unique (arr) {
 let len = arr.length;
  for (let i = 0; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      if (arr[i] === arr[j]) {
        arr.splice(j, 1);
        len--;
         j--;
      }
    }
  }
  return arr;
  }
//NaN和{}没有去重


3.indexof去重
遍历数组判断新数组中有没有当前项,没有放入新数组
indexOf方法获取到指定的字符在字符串中第一次出现的位置,没有出现等于-1
function unique (arr) {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (res.indexOf(arr[i]) === -1) {
        res.push(arr[i]);
    }
  }
  return res;
}
//NaN、{}没有去重


4.使用includes
includes() 此方法返回一个布尔值,判断给定的值是否存在于数组中
function unique (arr) {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) res.push(arr[i]);
  }
  return res;
}
//{}没有去重

5.sort() 方法去重
先排序,相邻两项比较,若不相同放新数组,否则忽略
function unique (arr) {
  arr.sort()  // 注意细节,sort()方法没有返回值,会改变可变对象
  const res = [arr[0]];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] !== arr[i-1]) {
        res.push(arr[i]);
    }
  }
  return res;
}
//NaN、{}没有去重


6.利用hasOwnProperty
function unique(arr){
      var obj = {}
      return arr.filter(function(item,index,arr){
        //typeof item + item是数组的元素item的类型与值拼接在一起,作为obj对象的属性,这个属性是唯一的
        //如果obj中有这个属性,则不会添加到obj对象上,达到去重的效果
        return obj.hasOwnProperty(typeof item + item)?false:(obj[typeof item +item] = true)
      })
    }

7.map去重
建一个空 Map 数据结构,遍历需要去重的数组,把数组的每一个元素作为 key 存到 Map 中。由于 Map 中不会出现相同的 key 值,所以最终得到的就是去重后的结果

function newArrFn (arr) {
      let newArr = []
      let map = new Map()
      for(let i = 0;i<arr.length;i++){
        // 如果 map里面不包含,就设置进去
        if (!map.has(arr[i])) {
          map.set(arr[i], true)
          newArr.push(arr[i])
        }
      };
      return newArr
    }
//{}空对象无法去重。


8. filter

function unique(arr) {
        return arr.filter(function (item, index, arr) {
            //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
            return arr.indexOf(item, 0) === index;
        });
    }
//{}没有去重、NaN两个都没有了

18、如何判断数据类型

有以下四种方式:typeof、instanceof、constructor、Object.prototype.toString.call()

  1. typeof

typeof用于判断原始值和引用值的类型: 对基础类型有效,对引用类型失效

  • typeof number/string/boolean/symbol/undefined // 返回对应类型
  • typeof 引用值 // 返回 ‘object’
  • 除了 Function 外的所有构造函数的类型都是 ‘object’
  • typeof null // ‘object’ 遗留的 bug

​ 2. instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,不能判断 null和undefined

  • 区分字面量和实例对象:
  • 字面量:var XXX = ‘yyy’ // ‘yyy’就是一个字面量: instanceof不能正确判断字面量类型
  • 实例对象:通过构造函数创建出来的对象为实例对象,字面量 -> 实例对象:new Array(xxx)

注意:如果 obj instanceof Foo 返回true,也不意味着永远返回 true,因为Foo.prototype属性的值有可能会改变,实例对象 obj 的原型链也可能会改变

let a = [];
a instanceof Array  // true
a instanceof Object // true
  1. constructor属性

返回创建此对象的数组函数的引用

用 constructor 判断类型的缺陷:如果先创建一个对象,更改它的原型,之后创建该对象的实例,这种方式就不可靠了

function F() {};
var f = new F;
f.constructor == F // true

F.prototype = {a: 1}
var f = new F
f.constructor == F // false 
  1. Object.prototype.toString.call()终极大法
  • 返回一个字符串,字符串是一个数组的形式,数组的第二个参数就是变量类型
Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(11) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call([]) ; // [object Array]

19、类数组与数组的区别及转换

类数组:通常把拥有数值length属性和对应非负整数属性的对象看成一种类型的数组,不能直接调用Array所具有的方法

常见的类数组:arguments和DOM方法返回的元素筛选(getElementsByTagName, getElementsByClassName)

举个栗子:

  • 对象具有length属性,可遍历,两个条件缺一不可
  • var a = {0: “a”, “1”: “b”, “2”: “c”, length: 3}

类数组转为数组:

// 方法一:最原始的方法:借用一个空数组,遍历
var eles = document.getElementsByTagName('div')
var res = []
for(let i = 0; i < eles.length; i++) {
    res.push(eles[i])
}

// 方法二:Array.prototype.slice.call(eles)
var res = Array.prototype.slice.call(eles);

// 方法三:Array.from(eles)(有length属性、是可遍历对象,缺一不可)
var res = Array.from(eles);

// 方法四:Array.prototype.map.call()
var res = Array.prototype.map.call(a, ele => {
    return ele;
});

// 方法五:扩展运算符[...eles](有iterator接口的对象才可以用)
var res = [...eles];
复制代码

20、数组常见API

考察点:熟悉常见的API并了解其用法

注意:map、reduce、flat、slice、push、pop、shift、unshift、splice等常用的重点了解

concat、every、fill、filter、find、findIndex、flat、forEach、indexOf、join、keys、map、push、popreduce、reverse、shift、unshift、slice、splice

修改器方法:
  • pop(): 删除数组的最后一个元素,并返回这个元素
  • push():在数组的末尾增加一个或多个元素,并返回数组的新长度
  • reverse(): 颠倒数组中元素的排列顺序
  • shift(): 删除数组的第一个元素,并返回这个元素
  • unshift(): 在数组的开头增加一个或多个元素,并返回数组的新长度
  • sort(): 对数组元素进行排序,并返回当前数组
  • splice(): 在任意的位置给数组添加或删除任意个元素
访问方法:
  • concat(): 返回一个由当前数组和其它若干个数组或者若干个非数组值组合而成的新数组
  • join(): 连接所有数组元素组成一个字符串
  • slice(): 抽取当前数组中的一段元素组合成一个新数组
  • indexOf(): 返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1
  • lastIndexOf(): 返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1
迭代方法:
  • forEach(): 为数组中的每个元素执行一次回调函数,最终返回 undefined
  • every(): 如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false
  • some(): 如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false
  • filter(): 将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回
  • map(): 返回一个由回调函数的返回值组成的新数组

更多请参考Array原文

21、继承的实现方式及比较

继承的方式有:原型链继承、借用构造函数、组合继承、原型继承、寄生继承、寄生组合继承、增强型寄生组合继承

放一个链接供参考

22、防抖和节流

防抖和节流都是为了解决短时间内大量触发某函数而导致的性能问题

  • 防抖:在事件被触发 n 秒后执行回调函数,如果 n 秒内又被触发,则重新计时
  • 说明:防抖函数适用于更关注结果的操作,不太关注操作过程,常见的事件有input、keyup等

使用场景:用户在输入框中连续输入,输入完成后执行最后一次的查询请求;百度输入框在输入稍有停顿时才更新推荐热词;

function debounce(handler, delay) {
    var time;
    delay = delay || 1000
    return function(){
        var _args = arguments
        var _self = this

        time && clearTimeout(time)
        time = setTimeout(() => {
            handler.call(_self, _args)
        }, delay)
    }
}
复制代码
  • 节流:函数在大于执行周期时才执行,周期内调用不执行
  • 说明:节流函数限制目标函数的执行频率,适用于关注变化过程的操作,调整目标函数执行频率使得变化更加平滑,常用事件resize、scroll、mouseWheel、touchmove、mouseover等

应用:鼠标不断点击触发,mousedown单位时间只触发一次

function throttle(handler, delay) {
    var lastTime = Date.now()
    return function(){
        var _args = arguments
        var _self = this

        var curTime = Date.now()
        if (curTime - lastTime >= delay) {
            handler.apply(_self, _args)
            lastTime = Date.now()
        }
    }
}

加强版

  • 多次触发操作,最后一次的操作时间不够间隔,因此不会触发操作
  • 要求:最后总会执行一次
function throttle(handler, delay) {
    var lastTime = Date.now()
    var id;
    return function(){
        var _args = arguments
        var _self = this

        var curTime = Date.now()
        if (curTime - lastTime >= delay) {
            if (id){
                clearTimeout(id);
            }
            handler.apply(_self, _args)
            lastTime = Date.now()
        } else {
            id && clearTimeout(id);//把上一次的定时器清除
            id = setTimeout(() => {
                handler.apply(_self, _args)
            }, 2000);
        }
    }
}

23、作用域和作用域链

  • 作用域是指程序中定义变量的区域,该位置决定了变量的生命周期;通俗地讲,作用域就是变量与函数的可访问范围

ES6之前,ES的作用域只有全局作用域和函数作用域;ES6引入了块级作用域

  • 内部函数使用了外部变量时,JavaScript引擎会去外层作用域中查找,这个查找的链条就称为作用域链

24、JS的垃圾回收机制

javascript中的内存管理是自动执行的,而且是不可见的

  • 可达性:以某种方式可以访问或者可以用的值,被保证存储在内存中
  • 根:有一些基本的固有可达值,由于显而易见的原因无法删除
    • 本地函数的局部变量和参数
    • 当前嵌套调用链上的其他函数的变量和参数
    • 全局变量
    • 其它…
  • 可访问性:如果引用或引用链可以从根访问其他值,则认为该值是可访问的

内部算法:基本的垃圾回收算法称为“标记-清除”

  • 垃圾回收器获取根并“标记”它们
  • 访问并标记所有来自根的引用
  • 访问标记的对象并标记其引用。
  • 以此类推,直到有未访问的引用为止
  • 除了标记对象,所有对象都被删除

javascript引擎在垃圾回收的优化:

  • 分代回收:对象分为“新对象”和“旧对象”,新对象出现,工作结束后就会被清理干净,存在时间长的对象,就会很少接受检查
  • 增量回收:将垃圾回收分解为多个部分,分别执行 空闲时间收集:垃圾回收器只在CPU空闲时运行

垃圾:没有被引用的对象或者有对象引用,但对象之间为相互引用,根访问不到

如何回收:标记-清除算法

25、addEventListener和onClick区别

onClick:

  • onClick: 给元素多次绑定onclick事件,只有最后一次绑定会被触发,之前的会被覆盖;
  • onclick事件只在冒泡阶段捕获;

addEventListener:

  • addEventlistener绑定多次,就会触发多次;
  • 可以通过设置参数,来规定事件在冒泡期间和捕获期间触发;
  • 对任何DOM元素都有效,不只针对HTML元素有效

26、说下浏览器对象(BOM)有哪些

  1. window 对象——BOM 的核⼼,是 js 访问浏览器的接⼝,也是 ES 规定的 Global 对象
  2. location 对象:提供当前窗⼝中的加载的⽂档有关的信息和⼀些导航功能。既是window 对象属性,也是 document 的对象属性
  3. navigation 对象:获取浏览器的系统信息
  4. screen 对象:⽤来表⽰浏览器窗⼝外部的显⽰器的信息等
  5. history 对象:保存⽤⼾上⽹的历史信息

27、跨域、同源策略以及跨域解决方案

由于浏览器的 同源策略,在出现 域名、端口、协议有一种不一致时,就会出现跨域,属于浏览器的一种安全限制。

一、JSONP

说明:JSONP方法是利用script标签的src属性不受同源策略影像的特性,拥有此类特性的标签还有img, iframe,link

缺点:需要服务器端支持;只能发起get请求

  1. 在客户端定义一个回调方法,预定义对数据的操作;
  2. 把回调方法的名称通过URL传参的形式,提交到服务器的数据接口;
  3. 服务器数据接口组织好要发送给客户端的数据,再根据回调方法名称,拼接出一个调用这个方法的字符串,发送给客户端;
  4. 客户端收到字符串之后,当做script脚本解析执行,获取数据
image-20220814104758448

二、CORS(Cross-origin resource sharing)

服务器端设置响应头,控制是否允许跨域

优点:服务器端控制;支持各种请求方式

缺点:会产生额外请求(option预检请求)

三、 document.domain

只能用于二级域名相同的情况下,比如a.test.com和b.test.com 只需要给两个页面都添加document.domain=’test.com’,表示二级域名相同就可以实现跨域; 比如在域a.test.com中的一个网页a.html引入了b.test.com中的一个网页b.html,此时a.html不能操作b.html,因为document.domain不一样,在两个页面中加入document.domain=’test.com’就可以相互访问了

注意:二级域名必须相同,协议、端口要一致,否则无法使用document.domain跨域

四、 postMessage

html5引入的postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文档、多窗口、跨域消息传递

postMessage(data,origin)

data: 要传递的数据;origin:目标窗口的源(协议+主机+端口号)

五、 postMessage和onMessage

场景:可以用于iframe与父容器传递消息

六、 Nginx反向代理

反向代理:客服端访问服务器受限,通过设置中间代理,中间代理与服务器通信,再将响应发送给客户端;过程中客户端不知道中间代理的存在

28. JavaScript中的事件循环机制,宏任务与微任务

javascript是一门单线程语言,单线程就意味着在同一时刻只能有一个任务被执行,前一个任务结束,才会执行后一个任务。前一个任务耗时很长,后一个任务就不得不等待。

如果排队是因为计算量大,CPU忙不过来倒也算了,但很多时候CPU是空闲的,因为IO设备很慢,不得不等待结果输出,再往下执行。

JavaScript语言的设计者意识到,此时主线程完全可以不管IO设备,挂起处于等待中的任务,先处理排在后面的任务,等到IO设备返回了结果,再回来继续执行刚刚挂起的任务。

于是,所有任务可以分为两种,一种是同步任务,一种是异步任务。同步任务指的是,在主线程上排队执行的任务,只有当前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程,异步任务可以执行了,该任务才会进入主线程执行。

img

  1. 同步任务和异步任务分别进入主线程和Event Table并注册函数
  2. 当 Event Table 中指定的事情完成时,会将这个函数移入 Event Queue
  3. 主线程上执行栈为空,会去 Event Queue 中读取对应的函数,进入主线程执行
  4. 上述过程不断重复,也就是事件循环(Event Loop)

javascript引擎存在mointoring process进程,会持续不断地检查主线程执行栈是否为空,一旦为空,就会去Event Queue中检查是否有等待被调用的函数。

JavaScript中的宏任务与微任务

JavaScript除了广义上的同步任务异步任务,其对任务还有更精细的定义:

  • 宏任务(macro-tack):包括整体代码script, setTimeout, setInterval
  • 微任务(micor-tack):有Promise, process.nextTick

事件循环的顺序,决定js代码的执行顺序。

进入整体代码(宏任务)后,开始第一次循环,接着执行所有的微任务,完成第一轮循环;

接着从宏任务开始,找到其中的一个任务队列,执行完毕,再执行所有的微任务;

如此循环往复。

img

以百度春招面试题为例

console.log(1)
setTimeout(function() {
  console.log(2)
}, 0);
const intervalId = setInterval(function() {
  console.log(3)
}, 0)
setTimeout(function() {
  console.log(10)
  new Promise(function(resolve) {
    console.log(11)
    resolve()
  })
  .then(function() {
    console.log(12)
  })
  .then(function() {
    console.log(13)
    clearInterval(intervalId)
  })
}, 0);
 
Promise.resolve()
  .then(function() {
    console.log(7)
  })
  .then(function() {
    console.log(8)
  })
console.log(9)
复制代码
  1. 第一轮事件循环流程:

    • 整体script作为第一个宏任务进入主线程,遇到console.log,输出1
    • 遇到setTimeout, 其回调函数被分发到宏任务Event Queue中,记为setTimeout1,
    • 遇到setIntrval,其回调函数被分发到宏任务Event Queue中,记为setInterval1,
    • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中,记为setTimeout2,
    • 遇到Promise.resolve,then被分发到微任务Event Queue中,记为then1
    • 遇到console.log,输出9

    此时的Event Queue:

    宏任务微任务
    setTimeout1then1
    setInterval1
    setTimeout2
    • 上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和9 继续执行微任务,执行then1,输出7,8

第一轮事件循环正式结束,输出结果是1,9,7,8

  1. 第二轮事件循环从setTimeout1开始 首先输出2,此时没有微任务,继续执行下一个宏任务
  2. 第三轮事件循环从setInterval1开始 输出3,此时没有微任务,继续执行下一个宏任务

前三轮输出结果为1,9,7,8,2,3

  1. 第四轮事件循环从setTimeout2开始

    • 首先输出10,遇到了promise,首先输出 11
    • then被分发到微任务Event Queue中,记为then2
    任务类型任务名称
    微任务then2
    • 继续执行微任务then2,输出12,13,setInterval被清除

最终输出结果为1,9,7,8,2,3,10,11,12,13

29. 函数柯里化、实现一个add方法

function add(){
    let _outer = [...arguments];

    let sum = function(){
        let _inner = [...arguments];
        _outer = _outer.concat(_inner);
        return sum;
    }

    sum.toString = function(){
        return _outer.reduce((prev, cur) => {
            prev = prev + cur;
            return prev;
        }, 0)
    }

    return sum;
}
复制代码

30、Var和不Var有啥区别?

在一个函数中Var,在外部是访问不道德,所以我们需要闭包
如果不用Var,就是全局作用域了,外部可以访问,此时的上下文就是window

可迭代,意味着我能够通过 for…in 循环来访问该对象的所有属性. 还能通过 Object.keys() 方法获取该对象的所有属性名.
可修改,意味着我能修改该对象的所有属性的值,通过为这些属性赋予一个新值就能修改: ob.a = 1000;.
可配置,意味着我能修改属性的行为,让该对象的属性都是不可迭代的、不可修改的和不可配置的. 只有可配置的属性才能通过 delete 被删除.

深入:var与不var区别就在可配置属性上,声明一个全局变量,其实是给window增加一个属性,由于window对象是全局对象,默认可不加,全局作用域下,window也可以用this来代替
“delete”不可以删除那些可配置为false的属性,某些内置对象的属性是不可配置的,比如通过变量声明或者函数声明创建的全局对象的属性。

31、怎么看一个属性是对象自有的还是从原型链继承下来的?

其实就是问in和hasOwnProperty()操作

原型链上继承过来的属性无法通过hasOwnProperty检测到,返回false
in能检测原型链的属性,但for in通常却不行

32、use strict是什么意思?使用它区别是什么?

use strict是一种ECMAscript 5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行,
使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为。

默认支持的糟糕特性都会被禁用,比如不能用with,也不能在意外的情况下给全局变量赋值;
全局变量的显示声明,函数必须声明在顶层,不允许在非函数代码块内声明函数,arguments.callee也不允许使用;
消除代码运行的一些不安全之处,保证代码运行的安全,限制函数中的arguments修改,严格模式下的eval函数的行为和非严格模式的也不相同;
提高编译器效率,增加运行速度;
为未来新版本的Javascript标准化做铺垫。

33、什么是window对象? 什么是document对象?

  • window对象

    在全局作用域中声明的变量、函数都是window对象的属性和方法。

    window对象是相对于web浏览器而言的,依赖于浏览器,在浏览器中全局对象指的就是window对象

  • document对象

    document对象是window对象的一个属性,是显示于窗口内的一个文档。而window对象则是一个顶层对象,它不是另一个对象的属性。document可以理解为文档,就是你的网页,而window是你的窗口,就是你的浏览器包含的。

区别:
① window 指窗体。document指页面。document是window的一个属性。
② 用户不能改变 document.location(因为这是当前显示文档的位置)。但是,可以改变window.location (用其它文档取代当前文档)window.location本身也是一个对象,而document.location不是对象

34、js有哪几种创建对象的方式

对象字面量

var obj = {}

Object 构造函数

var obj = new Object()

工厂模式

function Person(name, age) {
    var o = new Object()
    o.name = name;
    o.age = age;
    o.say = function() {
        console.log(name)
    }
    return o
}

缺点: 每次通过Person创建对象的时候,所有的say方法都是一样的,但是却存储了多次,浪费资源

构造函数模式

function Person(name, age) {
    this.name = name
    this.age = age
    this.say = function() {
        console.log(name)
    }
}
var person = new Person('hello', 18)

构造函数模式隐试的在最后返回return this 所以在缺少new的情况下,会将属性和方法添加给全局对象,浏览器端就会添加给window对象,可以根据return this 的特性调用call或者apply指定this

原型模式

function Person() {}
Person.prototype.name = 'hanmeimei';
Person.prototype.say = function() {
  alert(this.name);
}
Person.prototype.friends = ['lilei'];
var person = new Person();

实现了方法与属性的共享,可以动态添加对象的属性和方法。但是没有办法创建实例自己的属性和方法,也没有办法传递参数

构造函数和原型组合

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.say = function() {
    console.log(this.name)
}
var person = new Person('hello')

35、js如何实现继承

原型链继承

function Animal() {}
Animal.prototype.name = 'cat'
Animal.prototype.age = 1
Animal.prototype.say = function() {console.log('hello')}

var cat = new Animal()

cat.name  // cat
cat.age  // 1
cat.say() // hello

最简单的继承实现方式,但是也有其缺点

  • 来自原型对象的所有属性被所有实例共享
  • 创建子类实例时,无法向父类构造函数传参
  • 要想为子类新增属性和方法,必须要在new语句之后执行,不能放到构造器中

构造继承

function Animal() {
    this.species = "动物"
}
function Cat(name, age) {
    Animal.call(this)
    this.name = name 
    this.age = age
}

var cat = new Cat('豆豆', 2)

cat.name  // 豆豆
cat.age // 2
cat.species // 动物

使用 call 或 apply 方法,将父对象的构造函数绑定在子对象上.

组合继承

function Animal() {
    this.species = "动物"
}

function Cat(name){
  Animal.call(this)
  this.name = name
}

Cat.prototype = new Animal() // 重写原型
Cat.prototype.constructor = Cat
如果没有Cat.prototype = new Animal()这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal.这显然会导致继承链的紊乱(cat1 明明是用构造函数Cat生成的),因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat

extends 继承 ES6 新增继承方式,Class 可以通过 extends 关键字实现继承

class Animal {
    
}

class Cat extends Animal {
    constructor() {
        super();
  }
}
使用 extends 实现继承,必须添加 super 关键字定义子类的 constructor,这里的super() 就相当于 Animal.prototype.constructor.call(this)

参考:js中的继承

36、什么是内存泄漏,哪些操作会造成内存泄漏

内存泄漏:是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束

可能造成内存泄漏的操作:

  • 意外的全局变量
  • 闭包
  • 循环引用
  • 被遗忘的定时器或者回调函数

你可能还需要知道 垃圾回收机制

37、什么是事件代理,它的原理是什么

事件代理:通俗来说就是将元素的事件委托给它的父级或者更外级元素处理

原理:利用事件冒泡机制实现的

优点:只需要将同类元素的事件委托给父级或者更外级的元素,不需要给所有元素都绑定事件,减少内存空间占用,提升性能; 动态新增的元素无需重新绑定事件

38、对AMD和CMD的理解,它们有什么区别

AMD和CMD都是为了解决浏览器端模块化问题而产生的,AMD规范对应的库函数有 Require.js,CMD规范是在国内发展起来的,对应的库函数有Sea.js

AMD和CMD最大的区别是对依赖模块的执行时机处理不同

1、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块

2、CMD推崇就近依赖,只有在用到某个模块的时候再去require

AMD-中文版 CMD-规范

39.async 函数以及 await 命令

async 函数是什么?一句话,它就是 Generator 函数的语法糖

语法糖是指在不影响功能的情况下,添加某种方法实现同样的效果,从而方便程序开发.Vue.js的v-bind和v-on指令都提供了语法糖,也可以说是缩写。

async 特点:

  • async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句

  • async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数

  • async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误

  • async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);

await 命令: await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123
await 命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象.也就是说就算一个对象不是Promise对象,但是只要它有then这个方法, await 也会将它等同于Promise对象

使用注意点:

  • await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中
  • 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错

参考:async 和 await

40、export 与 export default有什么区别

export 与 export default 均可用于导出常量、函数、文件、模块等

在一个文件或模块中,export、import 可以有多个,export default 仅有一个

通过 export 方式导出,在导出时要加 { },export default 则不需要

使用 export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名; export 加载的时候需要知道加载模块的变量名

export default 命令的本质是将后面的值,赋给 default 变量,所以可以直接将一个值写在 export default 之后

41.script 拥有的属性

1.在 <script> 元素中设置defer 属性,相当于告诉浏览器立即下载,但延迟执行

2.async只适用于外部脚本文件,并告诉浏览器立即下载文件,下载完成后立即执行。但与 defer不同的是,标记为 async 的脚本并不保证按照指定它们的先后顺序执行
  • async:可选,表示应该立即下载脚本并执行,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。只对外部脚本文件有效。

  • charset:可选。表示通过 src 属性指定的代码的字符集。由于大多数浏览器会忽略它的值,因此这个属性很少有人用。

  • defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。IE7 及更早版本对嵌入脚本也支持这个属性。

  • language: 已废弃。原来用于表示编写代码使用的脚本语言(如 JavaScript 、 JavaScript1.2 或 VBScript )。

  • src:可选。表示包含要执行代码的外部文件。

  • type:可选。可以看成是 language 的替代属性;表示编写代码使用的脚本语言的内容类型(也称为 MIME 类型)。虽然 text/javascript 和 text/ecmascript 都已经不被推荐使用,但人们一直以来使用的都还是 text/javascript 。实际上,服务器在传送 JavaScript 文件时使用的 MIME 类型通常是 application/x–javascript ,但在 type 中设置这个值却可能导致脚本被忽略。另外,在非IE浏览器中还可以使用以下值: application/javascript 和 application/ecmascript 。考虑到约定俗成和最大限度的浏览器兼容性,目前 type 属性的值依旧还是 text/javascript 。不过,这个属性并不是必需的,如果没有指定这个属性,则其默认值仍为text/javascript 。
    n 方法回调函数的参数

  • async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误

  • async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);

await 命令: await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123
await 命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象.也就是说就算一个对象不是Promise对象,但是只要它有then这个方法, await 也会将它等同于Promise对象

使用注意点:

  • await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中
  • 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错

参考:async 和 await

40、export 与 export default有什么区别

export 与 export default 均可用于导出常量、函数、文件、模块等

在一个文件或模块中,export、import 可以有多个,export default 仅有一个

通过 export 方式导出,在导出时要加 { },export default 则不需要

使用 export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名; export 加载的时候需要知道加载模块的变量名

export default 命令的本质是将后面的值,赋给 default 变量,所以可以直接将一个值写在 export default 之后

41.script 拥有的属性

1.在 <script> 元素中设置defer 属性,相当于告诉浏览器立即下载,但延迟执行

2.async只适用于外部脚本文件,并告诉浏览器立即下载文件,下载完成后立即执行。但与 defer不同的是,标记为 async 的脚本并不保证按照指定它们的先后顺序执行
  • async:可选,表示应该立即下载脚本并执行,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。只对外部脚本文件有效。
  • charset:可选。表示通过 src 属性指定的代码的字符集。由于大多数浏览器会忽略它的值,因此这个属性很少有人用。
  • defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。IE7 及更早版本对嵌入脚本也支持这个属性。
  • language: 已废弃。原来用于表示编写代码使用的脚本语言(如 JavaScript 、 JavaScript1.2 或 VBScript )。
  • src:可选。表示包含要执行代码的外部文件。
  • type:可选。可以看成是 language 的替代属性;表示编写代码使用的脚本语言的内容类型(也称为 MIME 类型)。虽然 text/javascript 和 text/ecmascript 都已经不被推荐使用,但人们一直以来使用的都还是 text/javascript 。实际上,服务器在传送 JavaScript 文件时使用的 MIME 类型通常是 application/x–javascript ,但在 type 中设置这个值却可能导致脚本被忽略。另外,在非IE浏览器中还可以使用以下值: application/javascript 和 application/ecmascript 。考虑到约定俗成和最大限度的浏览器兼容性,目前 type 属性的值依旧还是 text/javascript 。不过,这个属性并不是必需的,如果没有指定这个属性,则其默认值仍为text/javascript 。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值