一、基本数据类型和引用数据类型
Number、String、Boolean、Null、 Undefined、Symbol(ES6)
基本数据类型:按值存栈中,简单数据段,数据大小确定,内存空间大小可以分配
引用数据类型:存堆中,每个空间大小不一样,根据情况进行特定的配置。
引用类型数据,在栈中,存的实际上是对象在堆内存中的引用地址。
obj1赋值给obj2,实际上这个 堆内存对象 在栈中的引用地址,复制了一份给了obj2。
他们指向了同一个堆内存对象,所以修改obj2其实就是修改那个对象,所以通过obj1访问也能访问的到。
二.、闭包
1、闭包
闭包就是有权访问另一个函数作用域中的变量 的函数
闭包是种现象。chrome里,闭包指向被访问的
**在本质上,闭包是将函数内部和函数外部连接起来的桥梁。**
- **js垃圾回收机制:**js 中的变量和函数不再用后,会被自动js垃圾回收机制回收。
- 形成闭包的条件:有函数/作用域的嵌套;内部函数引用外部函数的变量/参数。
- **闭包的结果:**内部函数 用 外部函数的 那些变量和参数仍然会保存,用
return返回了此内部函数,上面的变量和参数不会被回收。 - 闭包的原因:
**返回的函数并非孤立的函数,而是连同周围的环境(AO)打了一个包,成了一个封闭的环境包,共同返回出来 ---->闭包。** - 函数的作用域,取决于声明时而不取决于调用时。
- 变量存储
function(){}、{}、[]存储的是一个地址。
2、闭包的4种表现形式
- 返回一个函数
- 作为函数参数传递
- 回调函数
- 非典型闭包
IIFE(立即执行函数表达式)
a.返回一个函数:这种形式的闭包在JS中非常非常常见。
var a = 1;
function foo(){
var a = 2;
// 这就是闭包
return function(){
console.log(a);
}
}
var bar = foo();
// 输出2,而不是1
bar();
复制代码
b.作为函数参数传递:无论通过何种手段将内部函数传递到它所在词法作用域之外,它都会持有对原始作用域的引用,无论在何处执行这个函数,都会产生闭包。
var a=1;
function foo(){
var a=2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
//这就是闭包
fn();
}
//输出2,而不是1
foo();
复制代码
c.回调函数:在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中,只要使用了回调函数,实际上就是在使用闭包
//定时器
setTimeout(function timeHandler(){
console.log('timer');
},100)
//事件监听
$('#container').click(function(){
console.log('DOM Listener');
})
复制代码
d.IIFE:IIFE(立即执行函数表达式)并不是一个典型的闭包,但它确实创建了一个闭包。
var a = 2;
(function IIFE(){
// 输出2
console.log(a);
})();
3、解决循环输出问题
for(var i = 1; i <= 5; i ++){
setTimeout(function timer(){
console.log(i)
}, 0)
}
//为什么会全部输出6?如何改进,让它输出1,2,3,4,5?(方法越多越好)
复制代码
代码分析
for循环创建了5个定时器,并且定时器是在循环结束后才开始执行
for循环结束后,用var i定义的变量i此时等于6依次执行五个定时器,都打印变量
i,所以结果是打印5次6
**第1种改进:利用IIFE(立即执行函数表达式)**当每次for循环时,把此时的i变量传递到定时器中
for(var i=1;i<=5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
},0)
})(i)
}
复制代码
**第2种改进:**setTimeout函数的第三个参数,可以作为定时器执行时的变量进行使用
for(var i=1;i<=5;i++){
setTimeout(function timer(j){
console.log(j)
}, 0, i)
}
复制代码
第3种改进(推荐):在循环中用let i代替****var i
for(let i=1;i<=5;i++){
setTimeout(function timer(){
console.log(i)
}, 0)
}
三、构造函数的成员
非静态成员是指,每个实例对象中都保存属于该实例自身的同名变量/方法。通过实例对象操作它们的时候,不会印象其它实例里的相应成员。
静态成员是指,类的成员变量/方法,属于类本身,而非某个对象实例。
也就是说,各个实例对象用某个静态成员变量/方法用的是同一个副本
私有是指,实例对象的成员变量/方法,只能通过特定公共接口,供外部访问。无法通过操作对象本身获取。公有成员无此限制。
非静态+公有:this.xxx
静态+公有:构造函数.prototype.xxxx的变量/方法。
(牵一发而动全身,哪怕你是通过实例1.xxx修改了,实例2.xxx也跟着变)
非静态+私有:构造函数+闭包
静态+私有:外部私有作用域+闭包
构造函数:
- 实例成员:通过
this创建的属性和方法,this.xxx写的,只能通过 实例化对象 访问 - 静态成员:在
构造函数本身上直接添加的只能通过 构造函数本身 访问
Star.sex = '',Foo.a = 1; 调用的时候直接写Star.sex,Foo.a,不然取不到
当new实例的时候,Foo 函数内的属性、方法已初始化,覆盖了同名的静态方法
方法,是函数是复杂数据类型。
new多个实例对象,浪费了多个内存空间存放同一个函数。
↓
prototype: 让多个对象 共享同一个函数
四.原型链
- 每个对象都可以有一个原型__proto__,这个原型还可以有它自己的原型,以此类推,形成一个原型链。
- 查找特定属性,我们先去这个对象里找,没有的话就去它的原型对象里,还没有的话去向原型对象的原型对象里去寻找
a.原型对象 prototype
- 每个函数都有一个prototype属性。定义构造函数时自动创建,相当于个指针,从一个函数指向一个对象
- 它总是被__proto__所指,也是(构造)函数所创建的实例的原型对象prototype。
- 包含所有实例共享的属性和方法
(所有实例化对象都去同一个地址找某个方法,节省空间)
b.原型__proto__
-
对象独有(函数也有因为万物皆对象),为对象的查找机制 提供一个方向(路线)
-
__proto__总是指向prototype,应用原型链查询
c. '构造函数'constructor:
-
所有prototype****都有constructor属性,指向构造函数。相当于constructor属性是原始构造函数的引用 -
所有的实例对象都可以访问constructor属性,记录了实例对象到底引用哪个构造函数
(因为实例有__proto指向prototype__)
总结
所有对象都有__proto__
函数这个特殊对象,有__proto__属性,还有prototype
prototype对象默认有,constructor属性和__proto__属性。
prototype可以给函数和对象添加可共享(继承)的方法、属性,而
__proto__是查找某函数或对象的原型链方式。
constructor这个属性包含了一个指针,指回原构造函数。
d.实例化new的过程
var obj ={}; // 创建一个空对象
obj.__proto__ = Star.prototype; // 让obj的proto ——→ 构造函数的prototype
Star.call(obj); // 调用构造函数,并让this指向obj
return obj; // 返回这个对象
五.call apply bind
1.立即调用并改变this指向:
-
fn.call(obj,2,4)
-
fn.apply(obj,[2,4])
应用:
// push()一次一个元素,apply能让数组1添加数组2
Array.prototype.push.apply(array1, array2);
// 数组Array没有直接找最大值的方法,但Math下有
maxInNumbers = Math.max.apply(Math, arr);
//用Object下的toString验证任意 的类型 是不是数组
functionisArray(obj){
return Object.prototype.toString.call(obj) === '[object Array]' ;
}
//定义一个log方法,代理console.log,并前缀(app)
function log(){ 传入参数不确定,用arguments
var args = Array.prototype.slice.call(arguments); 伪数组 转换为 正常数组
args.unshift('(app)');
console.log.apply(console, args);
};
2.不调用但改变this指向
-
fn.bind(obj,2,5) "绑定"函数
bind的时候创建了一个新函数,传入的第一个参数作为this -
多次bind() 无效。
更深层次的原因, bind() 的实现,相当于所用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。