资讯专栏INFORMATION COLUMN

前端面试--javascript

int64 / 1312人阅读

摘要:运算符用来测试一个对象在其原型链中是否存在一个构造函数的属性。全局变量在页面关闭后销毁。同理,待第行执行完毕,即函数执行完毕后,调用函数所生成的上下文环境出栈,并且被销毁已经用完了,就要及时销毁,释放内存。

1. JavaScript的数据类型

1.1 JavaScript有几种类型的值

基本类型(值类型)

字符串(String)

数字(Number)

布尔(Boolean)

对空(Null)

未定义(Undefined)

独一无二的值(Symbol)

引用类型

对象(Object)

数组(Array)

函数(Function)

...

1.2 基本数据类型 1.2.1 值是不可变的
        var name = "jie";
        name.toUpperCase();
        console.log(name); //jie
1.2.2 存放在栈区

基本数据类型直接存储在栈(stack)中的简单数据段

为什么放入栈中存储

占据空间小

大小固定

属于被频繁使用数据

1.2.3 值的比较
        var a = 1;
        var b = true;
        console.log(a == b)  //true
        console.log(a === b); //false

== : 只进行值的比较,会进行数据类型的转换。

=== : 不仅进行值得比较,还要进行数据类型的比较。

1.3 引用数据类型 1.3.1 值是可变的
        var a = {
            age: 20
        }
        var b = a;
        b.age = 21;
        console.log(a.age)  //21
        console.log(b.age)  //21
1.3.2 同时保存在栈内存和堆内存

引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址

当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

1.3.3比较是引用的比较
        var a = {
            age: 20
        }
        var b = a;
        b.age = 21;
        console.log(a.age) //21
        console.log(b.age) //21

变量a初始化时,a指针指向对象{age:20}的地址,a赋值给b后,b又指向该对象{age:20}的地址,这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响。

如果取消某一个变量对于原对象的引用,不会影响到另一个变量

        var a = {
            age: 20
        }
        var b = a;
        a = 1;
        console.log(a)  //1
        console.log(b)  //{age:20}

a和b指向同一个对象,然后a的值变为1,这时不会对b产生影响,b还是指向原来的那个对象。

2.JavaScript检测 2.1 typeof

typeof返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、symbol、object、undefined、function等7种数据类型,

但不能判断null、array

typeof Symbol(); // symbol 有效
typeof ""; // string 有效
typeof 1; // number 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof new Function(); // function 有效
typeof null; //object 无效
typeof [] ; //object 无效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效
2.2 instanceof

instanceof 是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false。

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

不能检测null 和 undefined

[] instanceof Array; //true
{} instanceof Object;//true
new Date() instanceof Date;//true
new RegExp() instanceof RegExp//true
2.3 constructor

null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断

2.4Object.prototype.toString.call()
Object.prototype.toString.call("") ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
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(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window是全局对象global的引用
2.5 参考

https://github.com/ljianshu/B...

3. 函数(方法) 3.1 生成函数的三种方式 3.1.1 函数声明
        function fn(){
            console.log("aa")
        }
        fn()
3.1.2 函数表达式
        var fn = function() {
            console.log("aa")
        }
        fn()
3.1.3 立即执行函数
        var i = "aa";
        (function(i) {
            console.log(i)  //aa
        })(i)
        function fn(i) {
            (function() {
                console.log(i) //aa
            })()
        }
        fn("aa")
        function fn() {
            (function(i) {
                console.log(i)   // undefined
            })()
        }
        fn("aa")
        function fn() {
            (function() {
                console.log(i)  
            })(i)
        }
        fn("aa")

3.2 函数的作用域

函数的作用域分为全局作用

3.2.1先声明变量,再调用方法
        var a = "aa"
        fn()

        function fn() {
            var b = "bb"
            c = "cc"
            console.log(a)    //aa
            console.log(b)    //bb
            console.log(c)    //cc
        }
        console.log(a)    //a
        console.log(c)    //cc
        console.log(b)    //b is not defined
3.2.先调用方法,再声明变量
        fn()
        var a = "aa"
        function fn() {
            var b = "bb"
            c = "cc"
            console.log(a)    //aa
            console.log(b)    //bb
            console.log(c)    //cc
        }
        console.log(a)    //undefined
        console.log(b)    //b is not defined
        console.log(c)    //cc
3.3 变量生命周期

JavaScript 变量生命周期在它声明时初始化。

局部变量在函数执行完毕后销毁。

全局变量在页面关闭后销毁。

4. 原型到原型链 为什么会存在

因为js要实现继承,js没有像别的语言有继承这个东西(es6中的class本质上也是基于原型和原型链),

4.1名词解释

constructor 构造函数

prototype 原型(显式原型),只有函数才有 prototype 属性

__proto__ 原型链(隐式原型),每一个JavaScript对象(除了 null )都具有的一个属性,函数也是对象,所以函数也有__proto__

4.2 构造函数创建对象

4.2.1最简单的构造函数

函数名字为大写

与new配合

Person 就是一个构造函数,我们使用 new 创建了一个实例对象 person

        function Person() {

        }
        var person = new Person();
        person.name = "jie"
        console.log(person.name)
4.2.2 prototype(原型)
那什么是原型呢

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

每个函数都有一个 prototype 属性

函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 person1 和 person2 的原型

        function Person() {

        }
        Person.prototype.name = "jie"
        var person1 = new Person(); 
        var person2 = new Person(); 
        console.log(person1.name)    //jie
        console.log(person2.name)    //jie
用一张图表示构造函数和实例原型之间

4.2.3 proto

每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,

这个属性会指向该对象的原型

        function Person() {

        }
        var person = new Person();
        console.log(person.__proto__ === Person.prototype) //true
用一张图表示实例和实例原型之间的__proto__

4.2.4 constructor

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

        function Person() {

        }
        console.log(Person === Person.prototype.constructor) //true

4.3 实例与原型

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性

如果还查不到,就去找原型的原型,一直找到最顶层为止

        function Person() {

        }
        Person.prototype.name = "原型上的名字";
        var person1 = new Person();
        var person2 = new Person();
        person1.name = "实例名字1";
        person2.name = "实例名字2";
        console.log("person1.name:" + person1.name) //实例名字1
        console.log("person2.name:" + person2.name) //实例名字2

        delete person1.name;
        console.log("person1.name:" + person1.name) //原型上的名字
        console.log("person2.name:" + person2.name) //实例名字1
4.4 原型的原型

原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它

        var obj = new Object();
        obj.name = "jie"
        console.log(obj.name)

4.5 原型链
        function Person() {

        }
        var p1 = new Person();
        console.log(p1.__proto__ === Person.prototype)  //true
        console.log(Person.prototype.__proto__ === Object.prototype)    //true
        console.log(Object.prototype.__proto__ === null)    //true

图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

4.6 其它 4.6.1 person.constructor
function Person() {

}
var person = new Person();
console.log(person.constructor === Person); // true

当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:

person.constructor === Person.prototype.constructor
4.6.2 真的是继承吗

继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些

4.7 参考

https://github.com/mqyqingfen...

5.词法作用域

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域

        var value = 1;

        function fn1() {
            console.log(value)
        }

        function fn2() {
            var value = 2;
            fn1()
        }
        fn2()

按照函数栈先进后出的顺序执行,先执行完fn1(),再执行完fn2()

执行 fn1 函数,先从 fn1 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置(),查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1

6.执行上下文 6.1 声明(创建) JavaScript 变量

使用 var 关键词来声明变量

var carname;

变量声明之后,该变量是空的(它没有值)。
如需向变量赋值,请使用等号:

carname="Volvo";

不过,您也可以在声明变量时对其赋值:

var carname="Volvo";
6.2 变量声明的各种情况 6.2.1 打印未声明和未赋值
 console.log(a)

6.2.2 先打印,再声明和未赋值
        console.log(a)    //undefined
        var a;
6.2.3 先打印,再声明和未赋值
        console.log(a)    //undefined
        var a = 10;
6.2.4 声明和未赋值,再打印,
        var a = 10;
        console.log(a)
6.3 函数声明的各种情况  6.3.1 函数声明
        console.log(f1)

        function f1() {

        }

6.3.2 函数表达式
        console.log(f2)
        var f2 = function() {

        }

6.4 this
console.log(this)

6.5 什么是执行上下文

执行上下文也叫做执行上下文环境
给执行上下文环境下一个通俗的定义:在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空

变量、函数表达式——变量声明,默认赋值为undefined;

this——赋值;

函数声明——赋值;

这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”

6.6 函数中的执行上下文

以下代码展示了在函数体的语句执行之前,arguments变量和函数的参数都已经被赋值。从这里可以看出,函数每被调用一次,都会产生一个新的执行上下文环境。因为不同的调用可能就会有不同的参数

       function fn(x) {
            console.log(arguments)
            console.log(x)
        }
        fn(10)
        fn(20)

6.6 总结

全局代码的上下文环境数据内容为

普通变量(包括函数表达式),如: var a = 10; 声明(默认赋值为undefined)
函数声明,如: function fn() { } 赋值
this 赋值

如果代码段是函数体,那么在此基础上需要附加

参数 赋值
arguments 赋值
自由变量的取值作用域 赋值
6.7 参考

http://www.cnblogs.com/wangfu...

7.执行上下文栈 7.1 什么是执行上下文栈

执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个
这是一个压栈出栈的过程——执行上下文栈

7.2 上下文栈的压栈、出栈过程

在执行代码之前,首先将创建全局上下文环境

然后是代码执行。代码执行到第12行之前,上下文环境中的变量都在执行过程中被赋值。

执行到第13行,调用bar函数。
跳转到bar函数内部,执行函数体语句之前,会创建一个新的执行上下文环境。

并将这个执行上下文环境压栈,设置为活动状态

执行到第5行,又调用了fn函数。进入fn函数,在执行函数体语句之前,会创建fn函数的执行上下文环境,并压栈,设置为活动状态。

待第5行执行完毕,即fn函数执行完毕后,此次调用fn所生成的上下文环境出栈,并且被销毁(已经用完了,就要及时销毁,释放内存)。

同理,待第13行执行完毕,即bar函数执行完毕后,调用bar函数所生成的上下文环境出栈,并且被销毁(已经用完了,就要及时销毁,释放内存)。

8. this 8.1 判断分析当前this

对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window

对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象

在构造函数模式中,类中(函数体中)出现的this.xxx=xxx中的this是当前类的一个实例

call、apply和bind:this 是第一个参数

箭头函数this指向:箭头函数没有自己的this,看其外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。

8.2 this的5种情况 8.2.1 直接调用foo()
        function foo() {
            console.log(this)
        }
        var a = 1;
        foo()

8.2.2 obj.foo()
        function fn() {
            console.log(this)
        }
        var obj = {
            fn: fn
        }
        obj.fn();

8.2.3 构造函数
        function CreateJsPerson(name, age) {
            //this是当前类的一个实例p1
            this.name = name; //=>p1.name=name
            this.age = age; //=>p1.age=age
            console.log(this)
        }
        var p1 = new CreateJsPerson("尹华芝", 48);

8.2.4 call、apply和bind
        function add(c, d) {
            console.log(this)
            return this.a + this.b + c + d;
        }
        var o = {
            a: 1,
            b: 3
        };
        add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
        add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

8.2.5 箭头函数
    
    

8.3 参考

https://github.com/ljianshu/B...

9. 作用域和上下文环境

作用域在函数定义时就已经确定了。而不是在函数调用时确定

9.1 程序执行时的,上下文环境

按照程序执行的顺序,一步一步把各个上下文环境

在加载程序时,已经确定了全局上下文环境,并随着程序的执行而对变量就行赋值

程序执行到第27行,调用fn(10),此时生成此次调用fn函数时的上下文环境,压栈,并将此上下文环境设置为活动状态。

行到第23行时,调用bar(100),生成此次调用的上下文环境,压栈,并设置为活动状态

执行完第23行,bar(100)调用完成。则bar(100)上下文环境被销毁。接着执行第24行,调用bar(200),则又生成bar(200)的上下文环境,压栈,设置为活动状态。

执行完第24行,则bar(200)调用结束,其上下文环境被销毁。此时会回到fn(10)上下文环境,变为活动状态。

执行完第27行代码,fn(10)执行完成之后,fn(10)上下文环境被销毁,全局上下文环境又回到活动状态。

9.2 参考

http://www.cnblogs.com/wangfu...

10. 自由变量到作用域链 10.1 自由变量

在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量

        var x = 10;

        function fn() {
            var b = 20;
            console.log(x + b)   //这里的x在这里就是一个自由变量
        }
而取x的值时,就需要到另一个作用域中取。到哪个作用域中取呢?

要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记

        var x = 10;

        function fn() {
            console.log(x)    //10
        }

        function show(f) {
            var x = 20;
            (function() {
                f()
            })()
        }
        show(fn)
10.2 作用域链

如果跨了一步,还没找到呢?——接着跨!——一直跨到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。

这个一步一步“跨”的路线,我们称之为——作用域链

取自由变量时的这个“作用域链”过程:(假设a是自由量)

现在当前作用域查找a,如果有则获取并结束。如果没有则继续;

如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;

不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;

跳转到第一步。

实例

第13行,fn()返回的是bar函数,赋值给x。执行x(),即执行bar函数代码。取b的值时,直接在fn作用域取出。取a的值时,试图在fn作用域取,但是取不到,只能转向创建fn的那个作用域中去查找,结果找到了。

11. 闭包 11.1 什么是闭包
闭包是指可以访问另一个函数作用域变量的函数,一般是定义在外层函数中的内层函数。
11.2 为什么需要闭包呢

局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。

11.3 特点

占用更多内存

不容易被释放

11.4作用

保护

保存

11.5 何时使用

既想反复使用,又想避免全局污染

11.6 如何使用

定义外层函数,封装被保护的局部变量。

定义内层函数,执行对外部函数变量的操作。

外层函数返回内层函数的对象,并且外层函数被调用,结果保存在一个全局的变量中。

11.7 实例

代码执行前生成全局上下文环境,并在执行时对其中的变量进行赋值。此时全局上下文环境是活动状态

执行第17行代码时,调用fn(),产生fn()执行上下文环境,压栈,并设置为活动状态。

执行完第17行,fn()调用完成

按理说应该销毁掉fn()的执行上下文环境,但是这里不能这么做。注意,重点来了:因为执行fn()时,返回的是一个函数。函数的特别之处在于可以创建一个独立的作用域。而正巧合的是,返回的这个函数体中,还有一个自由变量max要引用fn作用域下的fn()上下文环境中的max。因此,这个max不能被销毁,销毁了之后bar函数中的max就找不到值了。

因此,这里的fn()上下文环境不能被销毁,还依然存在与执行上下文栈中。

执行到第18行时
全局上下文环境将变为活动状态,但是fn()上下文环境依然会在执行上下文栈中。另外,执行完第18行,全局上下文环境中的max被赋值为100。如下图:

执行到第20行,执行f1(15),即执行bar(15),创建bar(15)上下文环境,并将其设置为活动状态

创建bar函数是在执行fn()时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到了

执行完20行就是上下文环境的销毁过程

11.8 闭包面试解析实例

按道理,调用fn1()之后,就会销毁fn1()

但是此时的result为 function(){console.log(n)},(result为fn1()的返回值)

如果要调用result(),但是result里面的n是自由变量(函数的特别之处在于可以创建一个独立的作用域,result函数体内没有定义n,要到他的上层作用域找),

在result的上层作用域fn1()里找到了n

因此result依赖fn1()中的n,所以fn1()在调用后,并不能销毁,fn1()中的n一直存在

        function fn1() {
            var n = 0;
            return function() {
                console.log(n)
            }
        }
        var result = fn1();
        result()
11.9 参考

http://www.cnblogs.com/wangfu...
https://zhuanlan.zhihu.com/p/...
http://www.ruanyifeng.com/blo...

12. new运算符的执行过程 12.1 步骤

用new Object() 的方式新建了一个对象 obj

取出第一个参数,就是我们要传入的构造函数。(此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数)

将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性

使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性

返回 obj,(判断返回的值是不是一个对象,如果是一个对象,我们就返回这个对象,如果没有,我们该返回什么就返回什么)

12.2 实例
        function Person(name, age) {
            this.name = name;
            this.age = age;
            this.call = function() {
                alert(this.name + this.age)
            }
        }
        var p1 = new Person("jie", 12)
        p1.call()
        function myNew3() {
            let obj = new Object();  //1
            let Constructor = [].shift.call(arguments);    //2
            obj.__proto__ = Constructor.prototype;        //3
            let result = Constructor.apply(obj, arguments);    //4
            if (result instanceof Object) {    //5
                return result
            } else {
                return obj;
            }
        }
        var p4 = myNew3(Person, "wei", 14)
        p4.call()
12.3 参考

https://github.com/mqyqingfen...

13.call 13.1 定义

通俗的理解为借用(一个对象没有这个方法,但是别的对象有,不想重复代码,所以借来用一下)

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

13.2 实例
        var obj = {
            value: "1"
        }

        function fn(name, age) {
            this.name = name;
            this.age = age;
            this.say = function() {
                alert(this.name + this.age)
            }
        }
         fn.call(obj, "jie", 10)
        obj.say()
13.3 call的模拟实现

将函数设为对象的属性

执行该函数

删除该函数

        Function.prototype.call2 = function(context) {
            var context = context || window;
            context.fn = this;
            var args = [];
            for (var i = 1; i < arguments.length; i++) {
                args.push(`arguments[${i}]`)
            }
            var result = eval(`context.fn(${args})`)
            delete context.fn;
            return result;
        }
        fn.call2(obj, "biao", 20)
        obj.say()
13.4 参考

https://www.cnblogs.com/moqiu...
https://github.com/mqyqingfen...

14. apply

apply与call类型,只是传参不一样

14.1 apply的模拟实现
        var obj = {
            value: "1"
        }

        function fn(name, age) {
            this.name = name;
            this.age = age;
            this.say = function() {
                alert(this.name + this.age)
            }
        }
        
        Function.prototype.apply2 = function(context, arr) {
            var context = Object(context) || window;
            context.fn = this;
            var result;
            if (!arr) {
                result = context.fn()
            } else {
                var args = [];
                for (var i = 0; i < arr.length; i++) {
                    args.push(`arr[${i}]`)
                }
                result = eval(`context.fn(${args})`)
            }
            delete context.fn;
            return result;
        }
        fn.apply(obj, ["jie", 10])
        obj.say()
        fn.apply2(obj, ["biao", 20])
        obj.say()
15. bind
var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

// 返回了一个函数
var bindFoo = bar.bind(foo); 

bindFoo(); // 1
16. arguments

是一个对应于传递给函数的参数的类数组对象。

        function foo(name, age, sex) {
            console.log(arguments)
        }
        foo("name", "age", "sex")

        function fn(...arguments) {
            console.log(arguments)
        }
        fn(1, 2, 3)

17 继承 17.1组合继承
      function Person() {
        this.name = "jie"
        this.color = ["red", "blue"]
      }
      Person.prototype.say = function() {
        console.log(this.name)
      }

      function Student(id) {
        Person.call(this)
        this.id = id
      }

      Student.prototype = new Person()
      Student.prototype.constructor = Student

      var s1 = new Student("1")
      console.log(s1.name)
      console.log(s1.color)
      s1.say()
      s1.color.push("yellow")
      console.log(s1.color)

      var s2 = new Student("2")
      console.log(s2.name)
      console.log(s2.color)
      s2.say()
      s2.color.push("white")
      console.log(s2.color)

https://www.cnblogs.com/sarah...

18 异步 18.1 单线程 18.1.1 什么是单线程

Javascript语言的执行环境是"单线程"(single thread)
所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。

单线程的优点

这种模式的好处是实现起来比较简单,执行环境相对单纯

单线程的缺点

坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

解决单线程的缺点

为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

同步模式

后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的

异步模式

每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

异步模式的情景
http请求

        $.ajax({
            url: "",
            success: function(data) {
                console.log(data)
            },
            error: function(error) {
                console.log(error)
            }
        })
18 异步 18.1 单线程 18.1.1 什么是单线程

Javascript语言的执行环境是"单线程"(single thread)
所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。

单线程的优点

这种模式的好处是实现起来比较简单,执行环境相对单纯

单线程的缺点

坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

解决单线程的缺点

为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

同步模式

后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的

异步模式

每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

18.1.2常见的异步操作

网络请求,如ajax http.get

IO 操作,如readFile readdir

定时函数,如setTimeout setInterval

18.2 回调函数(Callback)

// 1秒后打印出aa

        function fn1(callback) {
            setTimeout(function() {
                callback();
            }, 1000)
        }
        fn1(fn2)

        function fn2() {
            console.log("aa")
        }
18.3 回调地狱
ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})
18.3.1 callback封装
        function ajaxFn(url, callback) {
            $.ajax({
                method: "get",
                url: url,
                success: function(data) {
                    callback(data)
                },
                error: function(error) {
                    console.log(error)
                }
            })
        }
        ajaxFn("https://cnodejs.org/api/v1/topic/5433d5e4e737cbe96dcef312", (res) => {
            console.log(res)
            console.log("第一个请求完成")
            ajaxFn("https://cnodejs.org/api/v1/topics", (ress) => {
                console.log(ress)
                console.log("第二个请求完成")
            })
        })

18.3.2 promise封装
        function promiseFn(url) {
            return new Promise((resolve, reject) => {
                $.ajax({
                    method: "get",
                    url: url,
                    success: function(data) {
                        resolve(data)
                    },
                    error: function(error) {
                        reject(error)
                    }
                })
            })
        }
        promiseFn("https://cnodejs.org/api/v1/topic/5433d5e4e737cbe96dcef312")
            .then(res => {
                console.log(res)
                console.log("第一个请求完成")
                return promiseFn("https://cnodejs.org/api/v1/topics")
            })
            .then(res => {
                console.log(res)
                console.log("第二个请求完成")
            })
            .catch(err => {
                console.log(err)
            })

18.3.3 async/await封装
        async function asyncFn(url) {
            return await new Promise((resolve, reject) => {
                $.ajax({
                    method: "get",
                    url: url,
                    success: function(response) {
                        resolve(response);
                    },
                    error: function(error) {
                        reject(error);
                    }
                })
            })
        }
        async function start() {
            var result1 = await asyncFn("https://cnodejs.org/api/v1/topic/5433d5e4e737cbe96dcef312");
            var result2 = await asyncFn("https://cnodejs.org/api/v1/topics");
            console.log(result1)
            console.log("第一个请求完成")
            console.log(result2)
            console.log("第二个请求完成")
        }
        start()

18.4 阻塞、非阻塞、同步、异步 18.4.1 生活中实例解析(热水壶烧水)

在很久之前,科技还没有这么发达的时候,如果我们要烧水,需要把水壶放到火炉上,我们通过观察水壶内的水的沸腾程度来判断水有没有烧开

随着科技的发展,现在市面上的水壶都有了提醒功能,当我们把水壶插电之后,水壶水烧开之后会通过声音提醒我们水开了。

18.4.2 同步、异步

对于烧水这件事儿来说,传统水壶的烧水就是同步的,高科技水壶的烧水就是异步的

18.4.3 阻塞、非阻塞

当你把水放到水壶里面,按下开关后,你可以坐在水壶前面,别的事情什么都不做,一直等着水烧好。你还可以先去客厅看电视,等着水开就好了。

对于你来说,坐在水壶前面等就是阻塞的,去客厅看电视等着水开就是非阻塞的。

18.4.4 阻塞、非阻塞和同步、异步的区别

阻塞、非阻塞说的是调用者(使用水壶的我),同步、异步说的是被调用者(水壶)。

19. 深浅拷贝 19.1 js 数据类型说起

基本数据类型保存在栈内存,

引用类型保存在堆内存中

19.1.1 栈内存,堆内存
var a = 1;//定义了一个number类型
var obj1 = {//定义了一个object类型
    name:"obj"
};

19.1.2 基本类型的复制
        var a = 1;
        var b = a;
        console.log(a) //1
        console.log(b)  //1
        b = 2;
        console.log(a)  //1
        console.log(b)  //2

赋值的时候,在栈内存中重新开辟内存,存放变量b,所以在栈内存中分别存放着变量a、b各自的值,修改时互不影响

19.1.3 引用类型的复制
        var color1 = ["red", "blue"]
        var color2 = color1;
        console.log(color1)   //["red", "blue"]
        console.log(color2)   //["red", "blue"]
        color1.push("black");   
        color1.push("yellow");  
        console.log(color1) // ["red", "blue", "black", "yellow"]
        console.log(color2) //["red", "blue", "black", "yellow"]

color1与color2指向堆内存中同一地址的同一对象,复制的只是引用地址

因此,对于引用类型的复制,简单赋值无用,需要拷贝。拷贝存在两种类型:深拷贝与浅拷贝

19.2 浅拷贝

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
修改新对象会改到原对象

        var person = {
            p1: {
                name: "jie",
                age: 18
            },
            p2: "biao"
        }

        var person1 = person;
        person1.p1.name = "nine";

        console.log(person)    //nine
        console.log(person1)    //nine
19.3 深拷贝

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
修改新对象不会改到原对象

        function depClone(obj) {
            var result = JSON.parse(JSON.stringify(obj));
            return result;
        }
        
        var person = {
            p1: {
                name: "jie",
                age: 18
            },
            p2: "biao"
        }
        var person2 = depClone(person);
        person2.p1.name = "nine";
        console.log(person)  //jie
        console.log(person2) //nine
19.4 参考

https://www.cnblogs.com/136as...
https://github.com/mqyqingfen...

20 DOM事件机制 20.1 DOM事件级别

20.2 DOM 0级事件 20.2.1 最简单的DOM 0级事件



20.2.2 onclick="fn"要不要加()
没有()
button type="button" onclick="fn" id="btn">点我试试
    

不弹出框(没有执行alert())

有()
button type="button" onclick="fn" id="btn">点我试试
    

弹出框(执行alert())

20.3 DOM2级事件

第一个参数是监听动作

第二个参数监听事件

第三个参数false冒泡,true捕获



20.4 DOM3级事件

DOM3级事件在DOM2级事件的基础上添加了更多的事件类型

UI事件,当用户与页面上的元素交互时触发,如:load、scroll

焦点事件,当元素获得或失去焦点时触发,如:blur、focus

鼠标事件,当用户通过鼠标在页面执行操作时触发如:dbclick、mouseup

滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel

文本事件,当在文档中输入文本时触发,如:textInput

键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress

合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart

变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified

20.4 DOM事件流 20.4.1 为什么是有事件流?

假如在一个button上注册了一个click事件,又在其它父元素div上注册了一个click事件,那么当我们点击button,是先触发父元素上的事件,还是button上的事件呢,这就需要一种约定去规范事件的执行顺序,就是事件执行的流程。

浏览器在发展的过程中出现了两种不同的规范

9以下的IE浏览器使用的是事件冒泡,先从具体的接收元素,然后逐步向上传播到不具体的元素。

Netscapte采用的是事件捕获,先由不具体的元素接收事件,最具体的节点最后才接收到事件。

而W3C制定的Web标准中,是同时采用了两种方案,事件捕获和事件冒泡都可以。

20.5 DOM事件模型 20.5.1 什么是DOM事件模型

DOM事件模型分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。

捕获阶段:事件从window对象自上而下向目标节点传播的阶段;

目标阶段:真正的目标节点正在处理事件的阶段;

冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。

20.5.2 事件捕获

捕获是从上到下,事件先从window对象,然后再到document(对象),然后是html标签(通过document.documentElement获取html标签),然后是body标签(通过document.body获取body标签),然后按照普通的html结构一层一层往下传,最后到达目标元素。我们只需要将addEventListener的第三个参数改为true就可以实现事件捕获





    
    
    
    Document
    



    
爷爷
父亲
儿子

点击id为child1的div标签时(儿子框),打印的结果是爷爷 => 爸爸 => 儿子,。

20.5.2 事件冒泡




    
    
    
    Document
    



    
爷爷
父亲
儿子

点击id为child1的div标签时(儿子框),打印的结果是儿子=>爸爸=>爷爷

20.6 事件代理(事件委托) 20.6.1 事件代理含义和为什么要优化?

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

举个例子,比如一个宿舍的同学同时快递到了,一种方法就是他们都傻傻地一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个宿舍同学;
在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。

那么利用事件冒泡或捕获的机制,我们可以对事件绑定做一些优化。 在JS中,如果我们注册的事件越来越多,页面的性能就越来越差,因为:

函数是对象,会占用内存,内存中的对象越多,浏览器性能越差

注册的事件一般都会指定DOM元素,事件越多,导致DOM元素访问次数越多,会延迟页面交互就绪时间。

删除子元素的时候不用考虑删除绑定事件

20.6.2 优点

减少内存消耗,提高性能
如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。借助事件代理,我们只需要给父容器ul绑定方法即可,这样不管点击的是哪一个后代元素,都会根据冒泡传播的传递机制,把容器的click行为触发,然后把对应的方法执行,根据事件源,我们可以知道点击的是谁,从而完成不同的事。

动态绑定事件
在很多时候,我们需要通过用户操作动态的增删列表项元素,如果一开始给每个子元素绑定事件,那么在列表发生变化时,就需要重新给新增的元素绑定事件,给即将删去的元素解绑事件,如果用事件代理就会省去很多这样麻烦。

20.6.3 代码实例
    
  • 1
  • 2
  • 3
  • 4

这是常规的实现事件委托的方法,但是这种方法有BUG,当监听的元素里存在子元素时,那么我们点击这个子元素事件会失效,所以我们可以联系文章上一小节说到的冒泡事件传播机制来解决这个bug。改进的事件委托代码:

    
  • 1 aaaaa
  • 2 aaaaa
  • 3 aaaaa
  • 4
20.7 Event对象常见的方法和属性 20.7.1 event. preventDefault()

preventDefault阻止默认行为
如果调用这个方法,默认事件行为将不再触发。什么是默认事件呢?例如表单一点击提交按钮(submit)刷新页面、a标签默认页面跳转或是锚点定位等。

a标签默认页面跳转
链接
输入框最多只能输入六个字符
    
    
20.7.2 event.stopPropagation()

stopPropagation停止冒泡
event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行


    
爷爷
父亲
儿子

只打印console.log("btn click 1");


    
爷爷
父亲
儿子

点击儿子的时候,打印儿子,爸爸
点击爸爸的时候,打印爸爸

20.8 参考

https://juejin.im/post/5c71e8...
https://github.com/ljianshu/B...

21 随机生成一个长度为10的范围0-100不重复的数组
      function random() {
        var arr = []
        for (var i = 0; i < 100; i++) {
          //生成循环100次,生成100个数字。该方法最大的弊端,为了避免有重复的情况导致数组不足10个元素,所以生成较多的数字
          var num = Math.floor(Math.random() * 100) //生成0-100的随机整数
          if (arr.length == 0) {
            arr.push(num) //数组为空时直接放入数组
          } else {
            for (var j = 0; j < arr.length; j++) {
              //循环已存在的数组
              if (arr.join(",").indexOf(num) < 0 && arr.length < 10) {
                //判断已存在数组中是否已有刚生成的数字,如没有且数组长度不足10才将num放入arr
                arr.push(num) //这样又会导致生成的大部分数字被arr.length <= 10排除掉了,浪费性能
              }
            }
          }
        }
        return arr
      }
     let set = new Set()
      while (set.size < 10) {
        //多少
        set.add(Math.round(Math.random() * 10) + 0) //最大值,最小值
      }
      let arr4 = Array.from(set)
      console.log(arr4)
      var arr5 = new Array()
      while (arr5.length < 10) {
        var num = Math.round(180 * Math.random()) + 20
        var exists = false
        for (var i = 0, l = arr5.length; i < l; i++) {
          if (arr5[i] == num) {
            //判断是否已经存在
            exists = true //存在的话将true传给exists
          }
        }
        if (!exists) {
          //现在exist是true,!exists就是false,所以不执行这个if下面代码。
          arr5.push(num)
        }
      }
      console.log(arr5)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103974.html

相关文章

  • 你不能错过的前端面试题合集

    摘要:收集的一些前端面试题从面试题发现不足,进而查漏补缺,比通过面试更难得及各大互联网公司前端笔试面试题篇及各大互联网公司前端笔试面试题篇面试题个和个经典面试题前端开发面试题如何面试前端工程师很重要个变态题解析如何通过饿了么面试轻 收集的一些前端面试题 从面试题发现不足,进而查漏补缺,比通过面试更难得 1 BAT及各大互联网公司2014前端笔试面试题--Html,Css篇 2 BAT...

    ninefive 评论0 收藏0
  • 你不能错过的前端面试题合集

    摘要:收集的一些前端面试题从面试题发现不足,进而查漏补缺,比通过面试更难得及各大互联网公司前端笔试面试题篇及各大互联网公司前端笔试面试题篇面试题个和个经典面试题前端开发面试题如何面试前端工程师很重要个变态题解析如何通过饿了么面试轻 收集的一些前端面试题 从面试题发现不足,进而查漏补缺,比通过面试更难得 1 BAT及各大互联网公司2014前端笔试面试题--Html,Css篇 2 BAT...

    darkbaby123 评论0 收藏0
  • 前端资源系列(4)-前端学习资源分享&前端面试资源汇总

    摘要:特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 本以为自己收藏的站点多,可以很快搞定,没想到一入汇总深似海。还有很多不足&遗漏的地方,欢迎补充。有错误的地方,还请斧正... 托管: welcome to git,欢迎交流,感谢star 有好友反应和斧正,会及时更新,平时业务工作时也会不定期更...

    princekin 评论0 收藏0
  • JavaScript系列(四) - 收藏集 - 掘金

    摘要:函数式编程前端掘金引言面向对象编程一直以来都是中的主导范式。函数式编程是一种强调减少对程序外部状态产生改变的方式。 JavaScript 函数式编程 - 前端 - 掘金引言 面向对象编程一直以来都是JavaScript中的主导范式。JavaScript作为一门多范式编程语言,然而,近几年,函数式编程越来越多得受到开发者的青睐。函数式编程是一种强调减少对程序外部状态产生改变的方式。因此,...

    cfanr 评论0 收藏0
  • 深入理解js

    摘要:详解十大常用设计模式力荐深度好文深入理解大设计模式收集各种疑难杂症的问题集锦关于,工作和学习过程中遇到过许多问题,也解答过许多别人的问题。介绍了的内存管理。 延迟加载 (Lazyload) 三种实现方式 延迟加载也称为惰性加载,即在长网页中延迟加载图像。用户滚动到它们之前,视口外的图像不会加载。本文详细介绍了三种延迟加载的实现方式。 详解 Javascript十大常用设计模式 力荐~ ...

    caikeal 评论0 收藏0
  • 前端开发面试题链接

    摘要:手册网超级有用的前端基础技术面试问题收集前端面试题目及答案汇总史上最全前端面试题含答案常见前端面试题及答案经典面试题及答案精选总结前端面试过程中最容易出现的问题前端面试题整理腾讯前端面试经验前端基础面试题部分最新前端面试题攻略前端面试前端入 手册网:http://www.shouce.ren/post/index 超级有用的前端基础技术面试问题收集:http://www.codec...

    h9911 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<