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 console.log(a === b) //true
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}的地址,这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响。
1.3.3.1 leetcode 链表结点的删除
const deleteDuplicates = function (head) {
// 设定 cur 指针,初始位置为链表第一个结点
let cur = head; //浅拷贝
// 遍历链表
while (cur != null && cur.next != null) {
// 若当前结点和它后面一个结点值相等(重复)
if (cur.val === cur.next.val) {
// 删除靠后的那个结点(去重)
cur.next = cur.next.next; //修改对象内的属性会对浅拷贝的对象也进行修改
} else {
// 若不重复,继续遍历
cur = cur.next; //赋值不会对浅拷贝的对象有影响
}
// console.log('head', JSON.stringify(head))
}
return head;
};
如果取消某一个变量对于原对象的引用,不会影响到另一个变量
var a = {
age: 20
}
var b = a;
a = 1;
console.log(a) //1
console.log(b) //{age:20}
a和b指向同一个对象,然后a的值变为1,这时不会对b产生影响,b还是指向原来的那个对象。
1.3.4 一道面试题
function test(person) {
person.age = 26
person = {
name: 'hzj',
age: 18
}
return person
}
const p1 = {
name: 'fyq',
age: 19
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?
p1:{name: “fyq”, age: 26}
p2:{name: “hzj”, age: 18}
1.4 类型转换
在 JS 中类型转换只有三种情况
- 转换为布尔值
- 转换为数字
- 转换为字符串
对象转基本类型
toString(),valueOf()
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****运算符**用于检测构造函数的
prototype 属性是否出现在某个实例对象的原型链上。
- 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的引用
null
null 可用于vue的props中文件上传的类型
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) ///undefined
console.log(b) //bb
console.log(c) //cc
}
console.log(a) //aa
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);
var age = 99;
function PersonX() {
this.age = 0;
setTimeout(() => {
this.age++;
console.log(age) //1
}, 1000);
}
PersonX();
var x = 11;
var obj = {
x: 22,
methods: {
x: 33,
say: function () {
console.log(this.x)
},
say2: () => {
console.log(this.x)
}
}
}
obj.methods.say(); //33
obj.methods.say2(); //11
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)上下文环境被销毁,全局上下文环境又回到活动状态。
![clipboard.png](/img/bVbsfD2)
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。如下图:
![clipboard.png](/img/bVbsfTs)
执行到第20行,执行f1(15),即执行bar(15),创建bar(15)上下文环境,并将其设置为活动状态
创建bar函数是在执行fn()时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到了
- 执行完20行就是上下文环境的销毁过程
demo
function fntest() {
var max = 10
return function bar(x) {
if (x > max) {
console.log(x)
}
}
}
var f1fntest = fntest() //执行完这块代码后,全局Global上有一个变量f1fntest
f1fntest(15) //当执行这行代码时,除了全局Global,当前Local,还有一个闭包Closure,这三个执行上下文环境并列,同时存在
f1fntest(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...
https://www.imooc.com/article/68898?block\_id=tuijian\_wz
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)
22 js字符串中如何判断出现最多的字符是哪一个
var str = "abbcccddddeeee";
function max() {
var json = {};
for (var i = 0; i < str.length; i++) {
var k = str[i]; //k是所有字符,字符串也跟数组一样可以通过中括号下标方法取到每个子元素
if (json[k]) {
json[k]++; //json里有这个字符时,把这个字符的数量+1,
} else {
json[k] = 1; //否则把这个字符的数量设为1
}
}
console.log(json)
var num = 0;
var value = null;
for (var k in json) { //s、f、g、t、d
if (json[k] > num) {
num = json[k];
value = k;
}
}
alert("出现最多的字符是:" + value + ',出现次数是:' + num);
};
max(str);
23. js的四种for循环
参考
https://www.cnblogs.com/rogerwu/p/10738776.html
https://juejin.im/entry/5a1654e951882554b8373622
24. var,let,const
24.1 字面理解
- let,变量,用于替代var
- const,常量
24.2 为什么引入了let,const
ES5只有全局作用域和函数作用域,没有块级作用域。
24.3
var a = 1;
function fn(){
var a = 2;
console.log(a) //2
}
fn()
console.log(a) //1
var a = 1;
function fn(){
a = 2;
console.log(a) //2
}
fn()
console.log(a) //2
var a = 1;
function fn(a){
var a = 2; //此时a为传进来的参数
console.log(a) //2
}
fn(a)
console.log(a) //1
var a = 1;
function fn(a){
a = 2;//此时a为传进来的参数
console.log(a) //2
}
fn(a)
console.log(a) //1
function f1(a) {
console.log(a); //10
var a=1;
console.log(a); //1
console.log(arguments[0]); //1
}
f1(10)
var a = 0;
function fn() {
console.log(a) // undefined
// console.log(b) // defined
var a = b = 1; //b相当于全局变量
console.log(b) //1
}
fn()
console.log(a) //0
console.log(b) //1
var a = 0;
function fn(a) {
console.log(a) //0
var a = b = 1;
console.log(b) //1
}
fn(a)
console.log(a) //0
console.log(b) //1
24.4 参考JavaScript 的变量作用域
25. 扩展运算符rest运算符
25.1 参考
https://www.cnblogs.com/mengfangui/archive/2017/12/15/8041317.html
26. 箭头函数和普通函数的区别
27. require和import的区别
import最终会编译成require
27.1 require
- 运行时加载
- 拷贝到本页面
- 全部引入
27.2 import
- 编译时加载
- 只引用定义
- 按需加载
https://segmentfault.com/a/1190000014434944
28 export default 和 export 区别
https://www.jianshu.com/p/eda...
遍历器Iterator
遍历器Iterator与map结合使用
var map2 = new Map()
map2.set('a', 1)
map2.set('b', 2)
var keys = map2.keys()
console.log('map2', map2)
console.log('keys', keys)
//错误1
// let b = map2.get('b')
// console.log('keys.next()', a.next())
// 错误2
// console.log('keys.next()', keys.next().next())
// 正确
console.log('keys.next()', keys.next())
console.log('keys.next()', keys.next())
Map 和 WeakMap 区别以及使用场景
Map 和 WeakMap 的区别
- Map 的键可以是任意类型,WeakMap 只接受对象作为键(null除外),不接受其他类型的值作为键
- Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键; WeakMap 的键是弱引用,键所指向的对象可以被垃圾回收,此时键是无效的
- Map 可以被遍历, WeakMap 不能被遍历
WeakMap
WeakMap是 ES6 中新增的一种集合类型,叫做“弱映射”,它和Map是兄弟关系,与Map的区别就在于这个弱字,API 还是Map的那套(只有set get has delete)
WeakMap 只能将对象作为键名,null也不行,
WeakMap 的使用场景
为 weakMap 不会影响垃圾回收,所以可以用来关联元数据
当上面代码执行后,登录按钮从DOM树中被删除了,但由于 Map 对节点对象是强引用关系,仍然保存着对按钮的引用,所以会引起内存泄漏
因此可以采用WeakMap当节点删除后,引用计数为0,等待垃圾回收机制回收
强引用
在上面的代码中,e1和e2是两个对象,通过arr数组对这两个对象添加一些文字说明。但是这样就形成了arr对e1和e2的引用,而这种引用又是强引用。它的区别就体现在。当我们不再需要这两个对象时,我们必须手动的删除这个引用,解除arr都两个对象的引用关系,否则垃圾回收机制不会释放e1和e2占用的内存。因为,arr仍然存在着对对象的引用!
麻烦的操作势必会造成问题,当忘记了手动删除引用,就会造成内存泄漏
弱引用
弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。
let obj = new WeakObject()
也就是说如果我们能这样创建一个弱引用的对象,我们就可以静静的等待垃圾车来把它拖走了,obj所引用的对象就会被回收
timer=null和clearInterval(timer) 不得不说的区别
- clearInterval(timer) 将定时器暂停,但是timer变量本身仍然存在.。达到保留对象的目的,以便再次使用
- 两个都能达到关闭定时器的目的。但是当timer = null后,timer会被系统的垃圾回收机制回收, 无法再重新启动定时器
- 用场:关闭定时器使用clearInterval(timer); 如果需要判断定时器是否存在而进行的一些操作,在清空定时器后需要使用timer=null
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
第一
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性
// CommonJS模块
let { stat, exists, readfile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
// ES6模块
import { stat, exists, readFile } from 'fs';
面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
第二
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成