前排提示:感谢@程序员小鹿 写的系列文章 这是我作的他文章的摘抄而已 用于学习html
String
Number
Null
Undefined
Symbol
Boolean
Object
Array
Function
Date
RegExp
stack
)和堆内存(heap
)Stack
自动分配内存,Heap
动态分配内存null
,减小内存消耗
stack
中,可按值直接访问stack
中存放引用地址,在heap
中存放具体对象typeof null === Object
javaScript
中数据二进制前三位都是0的话,系统就会断定为Object
类型。前三位 | 类型 |
---|---|
000 | 对象 |
1 | 整型 |
010 | 双精度 |
100 | 字符串 |
110 | 布尔 |
typeof
是一元运算符,返回String
类型。typeof
除了null
类型和对象类型不能准确判断,其余都能返回正确类型。
instanceof
判断某个对象是否是另外一个对象的实例,返回boolean
instanceof
用来测试一个对象在其原型链中是否存在一个构造函数的prototype
属性。javaScript
是弱类型语言,因此特定状况咱们须要类型转换即强制类型转换java
转
String
类型程序员
对于原始类型,转String
会默认调用toString()
方法web
String(123) //"123"
String(true) //"true"
String(null) //"null"
String(undefined) //"undefined"
String([1,2,3]) //"1,2,3"
String(function(){111}) //"function(){111}"
String({}) //[Object Object]
复制代码
转Boolean类型ajax
除了几个falsy值,''
、undefined
、null
、NAN
、0
、false
,其余都转为true
编程
转Number类型json
其余类型 | 数字类型 |
---|---|
字符串 | 1. 数字转化为数字 2.其余转化为NaN |
布尔类型 | true=>1,false=>0 |
null | 0 |
undefined | NaN |
数组 | 1.数组为空转化为0 2.数组只有一个元素转为该元素 3.其余转化为NaN |
空字符串 | 0 |
Number(10); // 10
Number('10'); // 10
Number(null); // 0
Number(''); // 0
Number(true); // 1
Number(false); // 0
Number([]); // 0
Number([1,2]); // NaN
Number('10a'); // NaN
Number(undefined); // NaN
复制代码
对象类型转原始类型数组
valueOf()
和toString()
方法String
,就调用toString()
valueOf()
toString()
加法运算浏览器
加法运算符是在运行时决定相加仍是链接,这被称为"重载"。bash
若是双方都不是String
Boolean+Boolean
会转化为数字相加Boolean+Number
,布尔转化为数字再加Object+Number
,对象类型调用valueOf
,若是不是String
,Boolean
或者Number
类型,则继续调用toString()
转化为字符串true + true // 2
1 + true // 2
[1] + 3 // '13'
复制代码
数组的valueOf()
返回的仍是数组自己,因此会继续调用toString()
字符串和字符串以及字符串与非字符串相加都会变链接
1 + 'b' // '1b'
false + 'b' // 'falseb'
'1' + 1 //'11'
复制代码
其余运算
其余算术运算符,减法除法乘法一概所有转为数字,再运算
1 * '2' // 2
1 * [] // 0
复制代码
&&
:全部条件为真,才为真||
:只要一个条件真,就真A&&B
若是A真就过,赋值为B;若是A假就不过,直接就是A
A||B
若是A为真就直接是A,若是A为假就为B
==
和===
===
是严格意义上的相等,必须类型和值都相等。
==
先判断两边类型是否相等,若是不等,先转换类型,再判断值是否相等。
this
就是一个对象,不一样状况this
指向不一样。
this
指向该对象var obj = {
name:'小鹿',
age: '21',
print: function(){
console.log(this)
console.log(this.name + ':' + this.age)
}
}
// 经过对象的方式调用函数
obj.print(); // this 指向 obj
复制代码
this
指向window
对象function print(){
console.log(this);
}
// 全局调用函数
print(); // this 指向 window
复制代码
new
的方式,this
永远指向新建立的对象function Person(name,age){
this.name = name
this.age = age
console.log(this)
}
var xiaohu = new Person('小胡',24)// this = > xiaohu
复制代码
this
箭头函数没有单独的this
值,其this
与声明所在的上下文相同。也就是说调用箭头函数的时候,不会隐式调用this
参数,而是从定义时的函数继承上下文
const obj = {
a:()=>{
console.log(this);
}
}
// 对象调用箭头函数
obj.a(); // window
复制代码
this
的指向能够经过调用函数的call
、apply
、bind
来改变this
指向
var obj = {
name:'小鹿',
age:'22',
adress:'小鹿动画学编程'
}
function print(){
console.log(this); // 打印 this 的指向
console.log(arguments); // 打印传递的参数
}
// 经过 call 改变 this 指向
print.call(obj,1,2,3);
// 经过 apply 改变 this 指向
print.apply(obj,[1,2,3]);
// 经过 bind 改变 this 的指向
let fn = print.bind(obj,1,2,3);
fn();
复制代码
共同点:
this
指向,且第一个参数都是this
指向的对象不一样点:
call
的传参是单个传递,apply
后续参数是数组形式,bind
均可以call
和apply
是直接执行,bind
会返回一个函数,在调用时才会执行箭头函数不能用call
,apply
改变this
指向,由于他没有本身的this
指向,若是用call
,apply
只会传递后续参数。
new的过程包括四个阶段:
// new 生成对象的过程
// 一、生成新对象
// 二、连接到原型
// 三、绑定 this
// 四、返回新对象
// 参数:
// 一、Con: 接收一个构造函数
// 二、args:传入构造函数的参数
function create(Con, ...args){
// 建立空对象
let obj = {};
// 设置空对象的原型(连接对象的原型)
obj._proto_ = Con.prototype;
// 绑定 this 并执行构造函数(为对象设置属性)
let result = Con.apply(obj,args)
// 若是 result 没有其余选择的对象,就返回 obj 对象
return result instanceof Object ? result : obj;
}
// 构造函数
function Test(name, age) {
this.name = name
this.age = age
}
Test.prototype.sayName = function () {
console.log(this.name)
}
// 实现一个 new 操做符
const a = create(Test,'小鹿','23')
console.log(a.age)
复制代码
经常使用建立对象的方式:
new
其余建立对象的方式:
Object.create()
字面量建立对象的优点:
new
同样,解析器须要顺着做用域链从当前做用域开始查找,若是在当前做用域找到了名为Object()
的函数就执行,若是没找到就顺着继续向上找,直到找到全局Object()
构造函数为止。Object()
构造函数能够接受参数,经过这个参数能够吧对象实例的建立过程委托给另外一个内置构造函数,并返回另一个对象实例,而这每每不是想要的。对于Object.create()
方式:Object.create(proto,[propertiesObject])
var People = function (name){
this.name = name;
};
People.prototype.sayName = function (){
console.log(this.name);
}
function Person(name, age){
this.age = age;
People.call(this, name); // 使用call,实现了People属性的继承
};
// 使用Object.create()方法,实现People原型方法的继承,而且修改了constructor指向
Person.prototype = Object.create(People.prototype, {
constructor: {
configurable: true,
enumerable: true,
value: Person,
writable: true
}
});
Person.prototype.sayAge = function (){
console.log(this.age);
}
var p1 = new Person('person1', 25);
p1.sayName(); //'person1'
p1.sayAge(); //25
复制代码
new
和字面量建立的对象的原型指向Object.prototype
,会继承Object
的属性和方法Object.create(null)
建立的对象,其原型指向null
,null
做为原型链的顶端,没有也不会继承任何属性和方法规定变量和函数的可以使用范围叫作做用域。
每一个函数都会有一个做用域,查找变量或函数时,由局部做用域到全局做用域依次查找,这些做用域的集合就叫作做用域链。
函数执行,造成一个私有的做用域,保护里面的私有变量不受外界干扰,除了保护私有变量外,还能够保存一些内容,这种模式叫作闭包。
再也不用到的内存,系统就回收。
内部函数引用着外部的函数的变量,外部函数尽管执行完毕,做用域也不会销毁。从而造成了一种不销毁的私有做用域。
内部函数能够访问外部函数做用域,外部函数不能获取内部函数的做用于变量。
一个函数里边再定义一个函数,内部函数一直保持有对外部函数做用域的访问权限。
两个做用:保护和保存。
// 事件绑定引起的索引问题
var btnBox = document.getElementById('btnBox'),
inputs = btnBox.getElementsByTagName('input')
var len = inputs.length;
for(var i = 0; i < len; i++){
inputs[i].onclick = function () {
alert(i)
}
}
复制代码
运行程序得出的结果都是len的数值。
由于全部事件绑定都是异步的,当触发点击事件,执行方法的时候,循环早就结束了。
同步:JS中当前这个任务没完成,下面的任务都不会执行,只有等当前完全完成,才会执行下面的任务。 异步:JS当前任务没有完成,须要等一会再完成,但此时咱们能够继续执行下面的任务。
解决方案:
当点击事件执行的时候,就会在私有做用域查找i的值,此时私有做用域没有i,就会去全局做用域查找,此时全局做用于的i已经被改变了,因此要建立一个私有做用域的i。
for(var i = 0;i<length;i++){
~function(i){
inputs[i].onclick = function(){
alert(this.myIndex)
}
}(i)
}
复制代码
闭包既有优势也有缺点。
优势是经过闭包解决循环几回就建立几个私有做用域,而后每一个私有做用域都有一个私有变量i。
缺点就是生成多个不销毁的私有做用域,对性能有影响。
原型:每一个JS对象都有__proto__属性,这个属性指向了原型。
原型链:多个对象经过__proto__的方式链接起来。
经过判断该对象的原型链中是否能够找到该构造类型的prototype类型
//一、当用调用 call 方法时,this 带边 son 。
//二、此时 Father 构造函数中的 this 指向 son。
//三、也就是说 son 有了 colors 的属性。
//四、每 new 一个 son ,都会产生不一样的对象,每一个对象的属性都是相互独立的。
function Father(){
this.colors = ["red","blue","green"];
}
function Son(){
// this 是经过 new 操做内部的新对象 {} ,
// 此时 Father 中的 this 就是为 Son 中的新对象{}
// 新对象就有了新的属性,并返回获得 new 的新对象实例
// 继承了Father,且向父类型传递参数
Father.call(this);
}
let s = new Son();
console.log(s.color)
复制代码
基本思想:在子类的构造函数的内部调用父类的构造函数。 优势:
缺点:
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
// 方法定义在原型对象上(共享)
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
// 子类继承父类的属性
Father.call(this,name); //继承实例属性,第一次调用 Father()
// 每一个实例都有本身的属性
this.age = age;
}
// 子类和父类共享的方法(实现了父类属性和方法的复用)
Son.prototype = new Father(); //继承父类方法,第二次调用 Father()
// 子类实例对象共享的方法
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5
var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10
复制代码
基本思想:
优势:
缺点:组合继承调用了两次父类的构造函数,形成了没必要要的消耗。
function object(o){
function F(){}
F.prototype = o;
// 每次返回的 new 是不一样的
return new F();
}
var person = {
friends : ["Van","Louis","Nick"]
};
// 实例 1
var anotherPerson = object(person);
anotherPerson.friends.push("Rob");
// 实例 2
var yetAnotherPerson = object(person);
yetAnotherPerson.friends.push("Style");
// 都添加至原型对象的属性(所共享)
alert(person.friends); // "Van,Louis,Nick,Rob,Style"
复制代码
基本思想:建立临时的构造函数,将传入的对象做为该构造函数的原型对象,而后返回新构造函数的实例。
浅拷贝:object产生的对象是不相同的,可是原型对象都是person对象,所改变存在原型对象的属性被全部生成的实例所共享,不只Person拥有,并且子类生成的实例也共享。
Object.create():在ECMAScript5中新增了此方法。
function createAnother(original){
var clone = object(original); // 经过调用object函数建立一个新对象
clone.sayHi = function(){ // 以某种方式来加强这个对象
alert("hi");
};
return clone; //返回这个对象
}
复制代码
基本思想:没必要为了指定子类的原型而调用父类的构造函数。
优势:解决组合继承中两次调用构造函数的开销。
再也不用到的内存,没有及时释放,就是内存泄漏。
之因此会有垃圾回收机制,是由于js中字符串、对象、数组等只有肯定固定大小时,才会动态分配内存,而使用完必须释放,不然会消耗完全部内存。
js与其余语言不一样,具备自动垃圾收集机制。
找出那些再也不使用的变量,而后释放内存,垃圾回收器会按照固定的时间间隔,周期性的执行垃圾回收。
垃圾回收器在运行的时候,会给存储在内存中的全部变量都加上标记,而后会去掉环境中变量以及被环境中的变量引用的变量的标记。剩下的就被视为要删除的变量。
其实现原理就是经过判断一个变量是否在执行环境中被引用,来进行标记删除。
跟踪记录每一个值被引用的次数。
当声明变量并将一个引用类型的值赋值给该变量时,则这个值引用次数加1,同一值被赋予另外一个变量,该值引用次数加1。当引用该值的变量被另外一个值取代,则引用计数减1,当计数为0时,就没法访问了,就会收回。
缺陷:两个对象的相互循环引用时,在函数执行完成后,两个对象相互引用计数并未归零,全部没法回收。
常见的就是在IE BOM和DOM中,使用的对象并非js对象,因此垃圾回收是基于计数策略。
虽然js内存都是自动管理,但仍是有问题,好比分配给web浏览器的可用内存数量一般比分配给桌面应用的少。
为了能让页面得到最好的性能,必须确保js变量占用最少的内存,因此最好将不用的变量引用释放,也叫解除引用。
var a = 20;
alert(a + 100);
var a = null;
复制代码
只有与环境变量失去引用的变量才会被标记回收,将对象引用设置为null,就失去引用,等待被回收。
对基本类型的拷贝就是对值进行拷贝,而对于引用类型来讲,拷贝的不是值,而是值的地址,最后两个变量的地址指向的是同一个值。
var a = 10;
var b = a;
b = 30;
console.log(a); // 10值
console.log(b); // 30值
var obj1 = new Object();
var obj2 = obj1;
obj2.name = "小鹿";
console.log(obj1.name); // 小鹿
复制代码
要想将obj1,obj2的关系断开,不让其指向同一个地址,分为浅拷贝和深拷贝。
本身实现一个浅拷贝:
function shallowClone(o){
const obj = {};
for(let i in o){
obj[i] = o[i]
}
return obj;
}
复制代码
let a = {c: 1}
let b = {...a}
a.c = 2
console.log(b.c) // 1
复制代码
let a = {c: 1}
let b = Object.assign({}, a)
a.c = 2
console.log(b.c) // 1
复制代码
深拷贝须要在浅拷贝的基础上加上递归
比较简单的实现方法:利用JSON.parse(JSON.stringify(obj))
function clonebyJSON(source){
return JSON.parse(JSON.stringify(source))
}
复制代码
但他内部也是使用的递归,递归到必定深度会爆栈,但不会出现循环引用问题。
因为JavaScript是单线程,因此会有阻塞问题,当一个任务执行完成后才能执行下一个任务,这样就会出现页面卡死。
单线程是由一些与用户的互动以及操做DOM相关的操做决定了JS要使用单线程,不然会带来复杂的同步问题。须要加锁。
H5标准规定容许js建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM。
最先是使用回调函数,回调函数不是直接调用,而是在特定的事件或条件发生时另外一方调用,用于对该事件或条件进行响应。
好比Ajax回调:
$.ajax({
type:'post',
url:'test.json',
dataType:'json',
success:function(res){
//成功回调
},
fail:function(err){
//响应失败回调
}
})
复制代码
可是若是某个请求存在依赖性:
$.ajax({
type:'post',
url:'test.json',
dataType:'json',
success:function(res){
$.ajax({
type:'post',
url:'xxx?id='+res.id,
success:function(res){
$.ajax({
...//循环
})
}
})
},
fail:function(err){
//响应失败回调
}
})
复制代码
这样就会不断循环嵌套,称为回调地狱。
缺点:
为何不能捕获异常?
这和js运行机制相关,异步任务执行完成会加入任务队列,当执行栈中没有可执行任务了,主线程去除任务队列中的异步任务并入栈执行,当异步任务执行时,捕获异常的函数已经在执行栈内退出了,因此异常没法捕获。
为何不能return
return只能终止回调函数的执行,而不能终止外部代码的执行。
ES6给了三种解决方案:Generator、Promise、async/await
能够理解为代码执行的环境。
JS执行上下文分为三种:
代码执行的时候,遇到一个执行上下文就将其依次压入执行栈。
当代码执行时,先执行位于栈顶的执行上下文中的代码,当栈顶的执行上下文代码执行完毕后就出栈,而后执行下一个位于栈顶的执行上下文。
通常包括:
通常包括:
nextTick队列会比Promise先执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>消息运行机制</title>
</head>
<body>
</body>
<script>
console.log('1');
setTimeout(() => {
console.log('2')
}, 1000);
new Promise((resolve, reject) => {
console.log('3');
resolve();
console.log('4');
}).then(() => {
console.log('5');
});
console.log('6');// 1,3,4,6,5,2
</script>
</html>
复制代码