JavaScript高级知识汇总(高级篇)「建议收藏」

JavaScript高级知识汇总(高级篇)「建议收藏」JavaScript知识总结(高级篇)1.深入基础1.1数据类型1.分类:-基本类型-String:任意字符串-Number:任意的数字-boolean:true/false-undefined:未定义-null:空-引用(对象)类型-Object:任意对象-Function:一种特别的对象(可以执行)-Array:一种特别的对象(数值下标,内部数据是有序的)2.判断:-typeof:可以判断:undefined、数值、字符串、布尔值不能判断:null与object

JavaScript高级知识总结(高级篇)

一、深入基础

1.1数据类型

1.分类:
-基本类型
-String:任意字符串
-Number:任意的数字
-boolean: true/false
-undefined:未定义
-null:空

-引用(对象)类型
-Object:任意对象
-Function:一种特别的对象(可以执行)
-Array:一种特别的对象(数值下标,内部数据是有序的)

2.判断:
-typeof:
可以判断:undefined、数值、字符串、布尔值
不能判断: null与object object与array
instanceof:可以判断对象的具体类型
=== 可以判断undefined、null

undefined与null的区别?
* undefined表示定义未赋值
* null表示赋值了,值为null
什么时候给变量赋值为null?
* 初始赋值,表明将要赋值为对象
* 结束前,让对象成为垃圾对象(被垃圾回收器回收)
3.严格区别变量类型与数据类型?
* 数据的类型
- 基本类型
- 对象类型
* 变量的类型(变量内存值的类型)
-基本类型:保存就是基本类型的数据
-引用类型:保存的是地址值

1.2数据变量与内存

1.什么是数据?
-存储在内存中代表特定的信息,本质上是010101…
-数据的特点:可传递、可运算
-内存中所有操作的目标:数据
逻辑运算
算数运算
赋值
运行函数

2.什么是内存?
-内存条通电后产生的可储存数据的空间(临时的)
-内存产生和死亡:内存条==>通电==>产生内存空间==>存储数据==>处理对象==>断电==>内存空间和数据消失
-一小块内存的2个数据
-内部存储的数据
-地址值

-内存分类
-栈:存储全局变量、局部变量
-堆:存储对象

3.什么是变量?
-可变化的的量,又变量名和变量值组成
-每个变量都对应的一小块内存,变量名用来查找对应的内存,变量值就是内存中保存的数据

4.内存,数据,变量三者之间的关系
-内存用来存储数据
-变量是内存的标识

5.var a = xxx; a内存中到底保存的是什么?
- xxx 是基本数据,保存的就是这个数据
- xxx 是对象,保存的是对象的地址值
- xxx 是一个变量,保存的xxx的内存内容(可能是基本数据,也可能是地址值)

6.关于引用变量的赋值问题:
2个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到的是修改之后的数据。

2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个对象。

<script> var obj1 = { 
    name:"tom"}; var obj2 = obj1; obj2.name = "bob"; console.log(obj1.name); //bob function fun(obj){ 
     obj.name = "TIMI"; }; fun(obj1); console.log(obj2.name); //TIMI var a = { 
    age:12}; var b = a; a = { 
    name:"mike",age:13}; b.age = 14; console.log(b.age,a.name,a.age); //14,mike,13 function fun2(obj){ 
     obj = { 
    age:15}; }; fun2(a); console.log(a.age); //13 </script>
只听到从架构师办公室传来架构君的声音:
破帽多情却恋头。有谁来对上联或下联?

7.在js调用函数时传递变量参数时,是值传递还是引用传递?
- 理解1:都是值(基本/地址值)传递
- 理解2:可能是值传递,也可能是引用传递(地址值)、

此代码由Java架构师必看网-架构君整理
<script> var a = 3; function fun1(a){ a = a+1; }; fun1(a); console.log(a);//3 function fun2(obj){ console.log(obj.name); }; var obj = { name:"tom"}; fun2(obj); </script>

8.js引擎如何管理内存
1.内存生命周期
- 分配小内存空间,得到它的使用权
- 储存数据,可以反复进行操作
- 释放小内存空间

2.释放内存
-局部变量:函数执行完自动释放
对象:成为垃圾对象–>垃圾回收器回收

1.3对象

1.什么是对象?
- 多个数据的封装体
- 用来保存多个数据的容器
- 一个对象代表现实中的一个事物

2.为什么用对象?
- 统一管理多个数据

3.对象的组成
- 属性:属性名(字符串)和属性值(任意)组成
- 方法:一种特别的属性(属性值是函数)

4.如何访问对象内部的数据?
- .属性名: 编码简单,有时不能用
- [“属性名”]: 编码麻烦,能通用

5.什么时候使用 [“属性名”] 这种方式
- 属性名包含特殊字符:- 空格等
- 属性名不确定

<script> var p = { 
    }; // 1.给对象p添加一个属性:content-type:text/json // p.content-type = "text/json"; 不能用 p["content-type"] = "text/json"; console.log(p["content-type"]); // 2.属性名不确定 var propName = "myAge"; var value = 18; // p.propName = value; 不能用 p[propName] = value; console.log(p[propName]); </script>

1.4函数

什么是函数?
实现特定功能的n条语句的封装体
只有函数是可以执行的,其他类型的数据不能执行

如何定义函数?
函数声明
表达式

如何调用函数?
test(); 函数名(); 直接调用
obj.test(); 通过对象调用
new test(); new调用
test.call/apply(obj); 临时让test成为obj的方法进行调用

回调函数

什么是回调函数?
1.自己定义的
2.没有调用
3.最终执行了(在某个时刻或某个条件下)

常见的回调函数?
1.dom事件回调函数
2.定时器的回调函数
3.ajax请求回调函数
4.生命周期回调函数

此代码由Java架构师必看网-架构君整理
<body> <button id="btn01">回调</button> <script> var btn01 = document.getElementById("btn01"); btn01.onclick = function(){ alert(this.innerHTML); }; // 定时器 setTimeout(function(){ alert("到点了"); },2000); </script> </body>

1.5 IIFE

IIFE 全称:Immediately-Invoked Function Expression (立即调用函数表达式),别名:匿名函数自调用。

作用:
- 隐藏内部实现
-不会干扰到外部(全局)命名空间
-用它来编码js模块

<script> (function(){ 
     //匿名函数自调用 var a = 3; console.log(a+3); })(); var a = 4; console.log(a); (function(){ 
     var a = 1; function test(){ 
     console.log(++a); } window.$ = function(){ 
     //向外暴露一个全局函数 return{ 
     test:test } }; })(); $().test(); //$是一个函数 //$执行后返回的是一个对象 </script>

1.6函数中的this

this是什么?
- 在任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
- 所有函数内部都有一个变量this
- 它的值是调用函数的当前对象

如何确定this的值?
- test(); window
- p.test(); p
- new test(); 新创建的对象
- p.call(obj); obj

<script type="text/javascript"> function Person(color) { 
     console.log(this) this.color = color; this.getColor = function () { 
     console.log(this) return this.color; }; this.setColor = function (color) { 
     console.log(this) this.color = color; }; } Person("red"); //this是谁? window var p = new Person("yello"); //this是谁? p p.getColor(); //this是谁? p var obj = { 
    }; p.setColor.call(obj, "black"); //this是谁? obj var test = p.setColor; test(); //this是谁? window function fun1() { 
     function fun2() { 
     console.log(this); } fun2(); //this是谁? window } fun1(); </script>

二、函数高级

2.1原型与原型链

2.1.1原型(prototype)

1.函数的prototype属性
- 每个函数都有一个prototype属性,它默认指向一个Object空对象(即为原型对象)
- 原型对象中有一个属性constructor,它指向函数对象

2.给原型对象添加属性(一般都是方法)
- 作用:函数的所有实例对象自动拥有原型中的属性(方法)

    <script> // 每个函数都有一个prototype属性,它默认指向一个Object空对象(即为原型对象) console.log(Date.prototype,typeof Date.prototype); function Fun(){ 
     } console.log(Fun.prototype);//默认指向一个Object空对象(没有我们的属性) // 原型对象中有一个属性constructor,它指向函数对象 console.log(Date.prototype.constructor===Date); console.log(Fun.prototype.constructor===Fun); // 给原型对象添加属性(一般是方法)--> 实例对象可以访问 Fun.prototype.test = function(){ 
     console.log('test()'); } var fun = new Fun(); fun.test(); </script>

2.1.2显示原型与隐式原型

1.每个函数function都有一个prototype,即显示原型(属性)
2.每个实例对象都有一个__proto__,可称为隐式原型(属性)
3.对象的隐式原型的值为其对应构造函数的显示原型的值
4.总结:
- 函数的prototype属性:在定义函数时自动添加的,默认值是一个空Object对象
- 对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
- 能直接操作显示原型,但不能直接操作隐式原型(ES6之前)

     <script> // 定义构造函数 function Fn(){ 
     // 内部语句:this.prototype = {} } // 1.每个函数function都有一个prototype,即为显示原型属性,默认指向一个空的Object对象 console.log(Fn.prototype); // 2.每个实例对象都有一个__protp__,可称为隐式原型 // 创建实例对象 var fn = new Fn(); // 内部语句:this.__proto__ = Fn.prototype console.log(fn.__proto__); // 3.对象的隐式原型的值为其对应构造函数的显示原型的值 console.log(Fn.prototype === fn.__proto__); //true // 给原型对象添加方法 Fn.prototype.test = function(){ 
     console.log("test()"); } // 通过实例调用原型的方法 fn.test(); </script>

显示原型与隐式原型的内存结构图
在这里插入图片描述

2.1.3原型链

原型链(别名:隐式原型链)
访问一个对象属性时,
先在自身属性中查找,找到返回
如果没有,再沿着__proto__这条链向上查找,找到返回
如果最终没有找到,则返回undefined

作用:
1.查找对象的属性(方法)
2.构造函数/原型/实体对象的关系(图解)
3.构造函数/原型/实体对象的关系2(图解)

     <script> // console.log(Object); // console.log(Object.prototype); console.log(Object.prototype.__proto__); function Fn(){ 
     this.test1 = function(){ 
     console.log("test1()"); }; } console.log(Fn.prototype); Fn.prototype.test2 = function(){ 
     console.log("test2()"); }; var fn = new Fn(); fn.test1(); fn.test2(); console.log(fn.toString()); console.log(fn.test3); // fn.test3(); /* 1.函数的显示原型指向的对象默认是空Object实例对象(但Object不满足) */ console.log(Fn.prototype instanceof Object); //true console.log(Object.prototype instanceof Object); //false console.log(Function.prototype instanceof Object); //true /* 2.所有函数都是Function的实例(包含Function) */ console.log(Function.__proto__ === Function.prototype); /* Object的原型对象是原型链的尽头 */ console.log(Object.prototype.__proto__); //null </script>

原型链图解
在这里插入图片描述

2.1.4原型链属性问题

1.读取对象的属性值时,会自动到原型链中查找
2.设置对象的属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
3.方法一般定义在原型中,属性一般通过构造函数定义在对象本身上

     <script> function Fn(){ 
     } Fn.prototype.a = "xxx"; var fn1 = new Fn(); console.log(fn1.a,fn1); var fn2 = new Fn(); fn2.a = "yyy"; console.log(fn1.a,fn2.a,fn2); function Person(name,age){ 
     this.name = name; this.age = age; } Person.prototype.setName = function(name){ 
     this.name = name; }; var p1 = new Person("Tom",12); p1.setName("libai"); var p2 = new Person("jack",12); p2.setName("nice"); console.log(p1); console.log(p2); console.log(p1.__proto__ === p2.__proto__); //true </script>

2.1.5探索instanceof

1.instanceof是如何判断的?
表达式:A instanceof B
如果B函数的显示原型对象在A对象的原型链上,返回true,否则返回false

2.Function是通过new自己产生的实例

     <script> /* 案例1 */ function Foo(){ 
    } var f1 = new Foo(); console.log(f1 instanceof Foo); //true console.log(f1 instanceof Object); //true /* 案例2 */ console.log(Object instanceof Function); //true console.log(Object instanceof Object); //true console.log(Function instanceof Function); //true console.log(Function instanceof Object); //true function Foo(){ 
    }; console.log(Object instanceof Foo); //false </script>

2.1.6原型面试题

<script> /* 测试题1*/ function A(){ 
    } A.prototype.n = 1; var b = new A(); A.prototype = { 
     n:2, m:3 }; var c = new A(); console.log(b.n,b.m,c.n,c.m); //1 undefined 2 3 /* 测试题2 */ function F(){ 
    } Object.prototype.a = function(){ 
     console.log("a()"); }; Function.prototype.b = function(){ 
     console.log("b()"); }; var f = new F(); f.a(); // f.b(); 不能执行 F.a(); F.b(); console.log(f); console.log(Object.prototype); console.log(Function.prototype); </script>

测试一图解
在这里插入图片描述

2.2执行上下文与执行上下文栈

2.2.1变量提升与函数提升

1.变量的声明提升

  • 通过var定义(声明)的变量,在定义语句之前就可以访问到,值为:undefined

2.函数声明提升

  • 通过function声明的函数,在之前就可以直接调用,值为:函数定义(对象)

3.问题:变量提升和函数提升是如何产生的?

<script> /* 面试题 */ // 输出a的值 var a = 3; function fn(){ 
     console.log(a); var a = 4; } fn(); //a=undefined console.log(b); //undefined 变量提升 fn2(); //可调用 函数提升 // fn3(); 不能调用,变量提升 var b = 3; function fn2(){ 
     console.log("fn2()"); } var fn3 = function(){ 
     console.log("fn3()"); }; </script>

2.2.2执行上下文

1.代码分类(位置)
- 全局代码
- 函数(局部)代码

2.全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
* var 定义的全局变量–>undefined,添加为window的属性
* function声明的全局函数–>赋值(fun),添加为window的方法
* this–>赋值(window)
- 开始执行全局代码

3.函数执行上下文
- 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
- 对局部数据进行预处理
* 形参变量–>赋值(实参)–>添加为执行上下文的属性
* arguments–>赋值(实参列表),添加为执行上下文属性
* var 定义的局部变量–>undefined,添加为执行上下文的属性
* function声明的函数–>赋值(fun),添加为执行上下文的方法
* this–>赋值(调用函数的对象)
- 开始执行函数体代码

<script> /* 全局执行上下文 */ console.log(a1,window.a1); //undefined,undefined a2(); //a2() console.log(this); //window var a1 = 3; function a2(){ 
     console.log("a2()"); } console.log(a1); //3 console.log("========="); /* 函数执行上下文 */ function fn(a1){ 
     console.log(a1); console.log(a2); a3(); console.log(this); console.log(arguments); //伪数组(2,3) var a2 = 3; function a3(){ 
     console.log("a3()"); } } fn(2,3); //2,undefined,a3(),window </script>

2.2.3执行上下文栈

1.在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中(压栈)
4.在当前函数执行完后,将栈顶的对象移除(出栈)
5.当所有的代码执行完之后,栈中只剩下window

<script> var a = 10; var bar = function(x){ 
     var b = 5; foo(x+b); }; var foo = function(y){ 
     var c = 5; console.log(a+c+y); }; bar(10); //30 </script>

2.2.4执行上下文2

 <script> /* 1.以下代码依次输出什么? gb:undefined fb:1 fb:2 fb:3 fe:3 fe:2 fe:1 ge:1 2.整个过程中产生了几次执行上下文? 5次 */ console.log("gb:"+i); var i = 1; foo(1); function foo(i){ 
     if(i==4){ 
     return; } console.log("fb:"+i); foo(i + 1); //递归调用 console.log("fe:"+i); } console.log("ge:"+i); </script>

2.2.5面试题

 <script> /* 测试题1 先执行变量提升,再执行函数提升 */ function a(){ 
    } var a; console.log(typeof a); //function /* 测试题2 */ if(!(b in window)){ 
     var b = 1; } console.log(b); //undefined /* 测试题3 */ var c = 1; function c(c){ 
     console.log(c); var c = 3; } c(2); //报错,c不是函数 </script>

2.3作用域与作用域链

2.3.1作用域

作用域
1.理解:
- 指一块空间,代码所在的区域
- 它是静态的(相对于上下文对象),在编写代码时就确定了

2.分类:
- 全局作用域
- 函数作用域
- 没有块作用域(ES6开始有了)

3.作用:
- 隔离变量,不同作用域下同名变量不会有冲突

<script> // 没有块作用域 /* if(true){ var c = 3; } console.log(c); */ var a = 10; var b = 20; function fn(x){ 
     var a = 100; c = 300; console.log("fn()",a,b,c,x); function bar(x){ 
     var a = 1000; var d = 400; console.log("bar()",a,b,c,d,x); } bar(100); bar(200); } fn(10); </script>

2.3.2作用域与执行上下文

作用域与执行上下文的区别:
区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时,
- 全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建
- 函数执行上下文是在调用函数时,函数体代码执行之前创建

区别2
- 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
- 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放

联系
- 上下文环境(对象)是从属于所在的作用域
- 全局上下文环境–>全局作用域
- 函数上下文环境–>对应的函数作用域

<script> var a = 10; var b = 20; function fn(x){ 
     var a = 100; c = 300; console.log("fn()",a,b,c,x); function bar(x){ 
     var a = 1000; var d = 400; console.log("bar()",a,b,c,d,x); } bar(100); bar(200); } fn(10); </script>

2.3.3作用域链

1.理解
- 多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的

2.查找一个变量的查找规则
- 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
- 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
- 再次执行2的相同操作,知道全局作用域,如果还找不到就抛出找不到的异常

<script> /* 作用域链 */ var a = 1; function fn1(){ 
     var b = 2; function fn2(){ 
     var c = 3; console.log(c); console.log(b); console.log(a); console.log(d); //d未定义,会报错 } fn2(); } fn1(); </script>

2.3.4作用域面试题

<script> var x = 10; function fn(){ 
     console.log(x); } function show(f){ 
     var x = 20; f(); } show(fn); //10 // fn的作用域没有x,只能从外部的作用域去找 console.log("========="); var fn = function(){ 
     console.log(fn); }; fn(); var obj = { 
     fn2:function(){ 
     console.log(fn2); //会报错,fn2未被定义 // console.log(this.fn2); 想要输出该作用域的fn2,须在前面加this. } }; obj.fn2(); </script>

2.4闭包

2.4.1理解闭包

1.如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包。

2.闭包到底是什么?
理解一:闭包是嵌套的内部函数
理解二:包含被引用变量(函数)的对象
注:闭包存在于嵌套的内部函数中

3.产生闭包的条件?
函数嵌套
内部函数引用了外部函数的数据(变量/函数)

<script> function fn1(){ 
     var a = 2; var b = "abc"; function fn2(){ 
     //执行函数定义就会产生闭包(不用调用内部函数) console.log(a); } fn2(); } fn1(); </script>

2.4.2常见的闭包

1.将函数作为另一个函数的返回值
2.将函数作为实参传递给另一个函数调用

<script> // 1.将函数作为另一个函数的返回值 function fn1(){ 
     var a = 2; function fn2(){ 
     a++; console.log(a); } return fn2; } var f = fn1(); f(); //3 f(); //4 // 2.将函数作为实参传递给另一个函数调用 function showDelay(msg,time){ 
     setTimeout(function(){ 
     alert(msg); },time); } showDelay("学 习",2000); </script>

2.4.3闭包的作用

闭包的作用
1.使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:
1.函数执行完后,函数内部声明的局部变量是否还存在?
一般是不存在的,存在于闭包的变量才可能存在

2.在函数外部能直接访问函数内部的局部变量么?
不能,但我们可以通过闭包让外部操作它

<script> function fn1(){ 
     var a = 2; function fn2(){ 
     a++; console.log(a); } function fn3(){ 
     a--; console.log(a); } return fn3; } var f = fn1(); f(); //1 f(); //0 </script>

2.4.4闭包的生命周期

1.产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
2.死亡:在嵌套的内部函数成为垃圾对象时

<script> function fn1(){ 
     // 此时闭包就已经产生了(函数提升,内部函数对象已经创建了) var a = 2; function fn2(){ 
     a++; console.log(a); } return fn2; } var f = fn1(); f(); //3 f(); //4 f = null; //闭包死亡(包含闭包的函数对象成为垃圾对象) </script>

2.4.5闭包的应用_自定义JS模块

闭包的应用:自定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包,执行n个方法的对象或函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能

<script src="myModule2.js"></script>
    <script> myModule2.doSomething(); myModule2.doOtherthing(); </script>

myModule2.js

(function(){ 
   
    // 私有数据
    var msg = "My Class";

    // 操作数据的函数
    function doSomething(){ 
   
        console.log("doSomething()"+msg.toUpperCase());
    }

    function doOtherthing(){ 
   
        console.log("doOtherthing()"+msg.toLowerCase());
    }

    // 向外暴露对象(给外部使用的方法)
    window.myModule2 = { 
   
        doSomething:doSomething,
        doOtherthing:doOtherthing
    }
})()

2.4.6闭包的缺点及解决

1.缺点:
- 函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
- 容易造成内存泄漏

2.解决
- 能不用闭包就不用
- 及时释放

<script> function fn1(){ 
     var arr = new Array[100000]; function fn2(){ 
     console.log(arr.length); } return fn2; } var f = fn1(); f(); f = null; //让内部函数成为垃圾对象-->回收闭包 </script>

2.4.7内存溢出与内存泄漏

1.内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余内存时,就会抛出内存溢出的错误

2.内存泄漏
- 占用的内存没有及视释放
- 内存泄漏积累的多了就容易导致内存溢出

常见的内存泄漏:
* 意外的全局变量
* 没有及时清理的计时器或回调函数
* 闭包

<script> // 内存溢出 var obj = { 
    }; for(var i = 0;i<10000;i++){ 
     obj[i] = new Array(1000000); console.log("-----"); } // 内存泄漏 // 意外的全局变量 function fn(){ 
     a = new Array(1000000); console.log(a); } fn(); // 没有及时清理的计时器或回调函数 var start = setInterval(function(){ 
     //启动定时器后不清理 console.log("---"); },2000); // 清理定时器 clearInterval(start); // 闭包 function fn1(){ 
     var a = 4; function fn2(){ 
     console.log(++a); } return fn2; } var f = fn1(); f(); // f = null;  </script>

2.4.8闭包面试题

<script> /* 以下代码输出的是什么 */ // 代码片段一 var name = "The Window"; var object = { 
     name:"My Object", getNameFunc:function(){ 
     return function(){ 
     return this.name; }; } }; alert(object.getNameFunc()()); // the window // 函数嵌套,但是内部函数没有调用外部函数的变量,不是闭包 // 代码片段二 var name2 = "The Window"; var object2 = { 
     name2:"My Object", getNameFunc:function(){ 
     var that = this; return function(){ 
     return that.name2; }; } }; alert(object2.getNameFunc()()); //My Object // 函数嵌套,内部函数调用了外部函数的that,是闭包 </script>
<script> function fun(n,o){ 
     console.log(o); return{ 
     fun:function(m){ 
     return fun(m,n); } } } var a = fun(0); a.fun(1); a.fun(2); a.fun(3); // undefined,0,0,0  var b = fun(0).fun(1).fun(2).fun(3); //undefined,0,1,2 var c = fun(0).fun(1); c.fun(2); c.fun(3); //undefined,0,1,1 </script>

三、对象高级

3.1对象的创建模式

3.1.1Object构造函数模式

方式一:Object构造函数模式
- 套路:先创建空Object对象,再动态添加属性/方法
- 使用场景:起始时不确定对象内部数据
- 问题:代码量多

<script> var p = new Object(); p.name = "tom"; p.age = 12; p.setName = function(name){ 
     this.name = name; }; // 测试 p.setName("李白"); console.log(p.name,p.age); </script>

3.1.2对象字面量模式

方式二:对象字面量模式
- 套路:使用{}创建对象,同时指定属性和方法
- 适用场景:起始时对象内部数据时确定的
- 问题:如果创建多个对象,有重复代码

<script> var p = { 
     name:"tom", age:16, setName:function(name){ 
     this.name = name; } }; // 测试 console.log(p.name,p.age); p.setName("Jack"); console.log(p.name,p.age); </script>

3.1.3工厂模式

方式三:
工厂模式
- 套路:通过工厂函数动态创建对象并返回
- 适用场景:需要创建多个对象
- 问题:对象没有一个具体的类型,都是Object类型

<script> function createPerson(age,name){ 
     //返回一个对象的函数-->工厂函数  var obj = { 
     age:age, name:name, setName:function(){ 
     this.name = name; } }; return obj; } var p1 = createPerson(16,"张三"); var p2 = createPerson(16,"李三"); console.log(p1); console.log(p2); function createCat(age,name){ 
     var obj = { 
     age:age, name:name, setName:function(){ 
     this.name = name; } }; return obj; } var c1 = createCat(15,"波斯猫") var c2 = createCat(15,"野猫") console.log(c1); console.log(c2); </script>

3.1.4自定义构造函数模式

方式四:自定义构造函数模式
套路:自定义构造函数,通过new创建对象
适用场景:需要创建多个类型确定的对象
问题:每个对象都有相同的数据,浪费内存

<script> // 定义类型 function Person(name,age){ 
     this.name = name; this.age = age; this.setName = function(){ 
     this.name = name; }; } var p1 = new Person("lisa",16); p1.setName("bob"); console.log(p1.name,p1.age); console.log(p1 instanceof Person); function Student(name,price){ 
     this.name = name; this.price = price; this.setName = function(){ 
     this.name = name; }; } var s = new Student("熊大",1000); console.log(s instanceof Student); var p2 = new Person("Jack",16); console.log(p1,p2) </script>

3.1.5构造函数+原型组合模式

方式五:构造函数+原型组合
套路:自定义构造函数,属性在函数中初始化,方法添加到原型上
适用场景:需要创建多个类型确定的对象

<script> function Person(name,age){ 
     this.name = name; this.age = age; } Person.prototype.setName = function(name){ 
     this.name = name; }; var p1 = new Person("tom",16); var p2 = new Person("bob",19); console.log(p1,p2); </script>

3.2继承模式

3.2.1原型链继承

原型链继承:方式一
套路:
1.定义父类型的构造函数
2.给父类型的原型添加方法
3.定义子类型的构造函数
4.创建父类型的对象赋值给子类型的原型
5.将子类型原型的构造属性设置为子类型
6.给子类型原型添加方法
7.创建子类型的对象:可以调用父类型的方法

关键
- 子类型的原型为父类型的一个实例对象

<script> // 父类型 function Supper(){ 
     this.supProp = "Supper property"; } Supper.prototype.showSupperProp = function(){ 
     console.log(this.supProp); } // 子类型 function Sub(){ 
     this.subProp = "Sub property"; } // 子类型的原型为父类型的一个实例对象 Sub.prototype = new Supper(); // 让子类型的原型的constructor指向子类型 Sub.prototype.constructor = Sub; Sub.prototype.showSubProp = function(){ 
     console.log(this.subProp); } var sub = new Sub(); sub.showSupperProp(); sub.showSubProp(); console.log(sub); //Sub </script>

原型链继承结构图:
在这里插入图片描述

3.2.2借用构造函数继承

方式二:借用构造函数继承(假的)
1.套路:
- 定义父类型的构造函数
- 定义子类型的构造函数
- 在子类型构造函数中调用父类型构造

2.关键:
- 在子类型构造函数中通用call()调用父类型构造函数

<script> function Person(name,age){ 
     this.name = name; this.age = age; } function Student(name,age,price){ 
     Person.call(this,name,age); //相当于:this.Person(name,age) /* this.name = name; this.age = age; */ this.price = price; } var s = new Student("tom",20,14000); console.log(s.name,s.age,s.price); </script>

3.2.3组合继承

方式三:原型链+借用构造函数的组合继承
1.利用原型链实现对父类型对象的方法继承
2.利用call借用父类型构造函数初始化相同属性

<script> function Person(name,age){ 
     this.name = name; this.age = age; } Person.prototype.setName = function(name){ 
     this.name = name; }; function Student(name,age,price){ 
     Person.call(this,name,age); //为了得到属性 this.price = price; } Student.prototype = new Person(); //为了能看到父类型的方法 Student.prototype.constructor = Student; //修正constructor属性 Student.prototype.setPrice = function(price ){ 
     this.price = price; }; var s = new Student("tom",24,15000); s.setName("bob"); s.setPrice(16000); console.log(s.name,s.age,s.price); </script>

四、线程机制与事件机制

4.1进程与线程

进程:程序的一次执行,它占有一片独有的内存空间,可以通过windows任务管理器查看进程
- 多进程运行:一应用程序可以同时启动多个实例运行

线程:是进程内的一个独立执行单元,试程序执行的一个完整流程,是CPU的最小调度单元

多线程:在一个进程内,同时有多个线程运行
优点:
1.有效提升CPU的利用率
缺点:
1.创建多线程开销
2.线程间切换开销
3.死锁与状态同步问题

单线程
优点:顺序编程简单易懂
缺点:效率低

相关知识
- 应用程序必须运行在某个进程的某个路线上
- 一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
- 一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
- 一个进程内的数据可以供其中的多个线程直接共享
- 多个进程之间的数据是不能直接共享的
- 线程池:保存多个线程对象的容器,实现线程对象的反复利用

4.2定时器引发的思考

1.定时器真的是定时执行的么?
- 定时器并不能保证真正定时执行
- 一般会延迟一点,也有可能延迟很长时间

2.定时器回调函数是在分线程执行的吗?
- 在主线程执行的,js是单线程的

3.定时器是如何实现的?
事件循环模型

 <button id="btn">启动定时器</button>
     <script> document.getElementById("btn").onclick = function(){ 
     var start = Date.now(); console.log("启动定时器前"); setTimeout(function(){ 
     console.log("定时器执行了",Date.now()-start); },200); } console.log("定时器启动后"); // 做一个长时间的工作 for(var i = 0;i<1000000000;i++){ 
     } </script>

4.3JS是单线程的

1.如何证明js执行是单线程的?
- setTimeout()的回调函数是在主线程执行的
- 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行

2.为什么js要用单线程模式,而不是用多线程模式?
- JavaScript的单线程,与它的用途有关
- 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM
- 这决定了它只能是单线程,否则会带来很复杂的同步问题

3.代码的分类:
- 初始化代码
- 回调代码

4.js引擎执行代码的基本流程
- 先执行初始化代码:包含一些特别的代码 回调函数(异步执行)
- 设置定时器
- 绑定事件监听
- 发送ajax请求
- 后面在某个时刻才会执行回调代码

4.4事件循环模型

1.所有代码分类
- 初始化执行代码(同步代码):包含绑定dom事件监听,设置定时器,发送ajax请求的代码
- 回调执行代码(异步代码):处理回调逻辑

2.js引擎执行代码的基本流程:初始化代码==>回调代码
3.模型的2个重要组成部分:
- 事件(定时器/DOM事件/Ajax)管理模块
- 回调队列

4.模型的运转流程
- 执行初始化代码,将时间回调函数交给对应模块管理
- 当事件发生时,管理模块会将回调函数及其数据添加到回调队列当中
- 只有当初始化代码执行完后(可能要一定时间),才会遍历读取回调队列中的回调函数执行

<button id="btn">测试</button>

     <script> function fn1(){ 
     console.log("fn1()"); } fn1(); document.getElementById("btn").onclick = function(){ 
     console.log("点击了btn"); }; setTimeout(function(){ 
     console.log("定时器执行了"); },2000); function fn2(){ 
     console.log("fn2()"); } fn2(); </script>

4.5Web Workers

1.h5规范提供了js分线程的实现,取名为:Web Workers
2.相关API
- Worker:构造函数,加载分线程执行的js文件
- Worker.prototype.onmessage: 用于接收另一个线程的回调函数
- Worker.prototype.postMessage: 向另一个线程发送消息

3.不足:
- worker内代码不能操作DOM
- 不能跨域加载js
- 不是每个浏览器都支持这个新特性

 <input type="text" placeholder="数值" id="number">
     <button id="btn">计算</button>
     <script> var input = document.getElementById("number"); document.getElementById("btn").onclick = function(){ 
     var number = input.value; // 创建一个Worker对象 var worker = new Worker("worker.js"); // 绑定接收消息的监听 worker.onmessage = function(event){ 
     console.log("主线程接收分线程返回"); alert(event.data); }; // 向分线程发送信息 worker.postMessage(number); console.log("主线程向分线程发送数据"+number); }; </script>

worker.js

function fibonacci(n){ 
   
    return n<=2? 1:fibonacci(n-1) + fibonacci(n-2); //递归调用
}

console.log(this);
var onmessage = function(event){ 
   
    var number = event.data;
    console.log("分线程接收到主线程发送的数据:"+number);

    // 计算
    var result = fibonacci(number);
    postMessage(result);
    console.log("分线程向主线程返回数据:"+result);

    // alert(result) alert是window的方法,在分线程不能调用
    // 分线程中的全局对象不再是window,所以在分线程中不可能跟新界面
};
架构君码字不易,如需转载,请注明出处:https://javajgs.com/archives/209912
0

发表评论