目录
补充理解
var c={name:'tom'};
console.log(c);
- c本身不是对象,是变量。只是说c能找到{name:'tom'};这个对象,c这个变量保存的是内存对象的地址
- var说明JavaScript是弱类型
- 在运行后,先编译后解析
- 内存怎么找?——>根据名称来找
- 什么样的内存需要他的地址?——>内存为对象。
- 需要c.name时,内存要有地址值数据和内存数据
var a={age:12};
var b=a;
a={name:'bob',age:13};
b.age=14;
console.log(b.age,a.name,a.age);
如何表述a和b的关系?
- 将a的内容传给了b,内容为a的地址值
- 从该例子可以看出,要让2个引用变量指向同一个对象,保存的是地址值
var obj={age:18};
function set(obj){
obj={age:12};
}
set(obj);
console.log(obj.age);
(易错)函数执行完,内部内容会被垃圾回收(对)
如何理解obj的内容输出没有发生改变呢?——>obj在加入函数后,堆地址改变了,但是他并没有改变之前obj里存的内容
var a=3;
var obj={};
var obj2=null;
- a占一个内存空间
- obj占两个
- obj2占一个
区分“释放”和“垃圾回收”
- 释放没有时间间隔,如在函数结束之后,内部内容就会被释放
- 垃圾回收有时间间隔,在某一段时间由垃圾回收箱释放空间
var propName='myAge';
var value=18;
p[propName]=value;
console.log(p[propName]);
如何理解变量名不确定的情况?
对象中的[]可以理解为函数的传参,所以他可以是个字符串也可以是一个变量,因此该案例中的p[propName]等价于p.myAge
函数
什么是函数?
- 实现特定功能的n条语句的封装体
- 只有函数是可以执行的,其他类型的数据不能执行
为什么要用函数?
- 提高代码复用
- 便于阅读交流
如何定义函数?
- 函数声明
- 表达式
如何调用(执行)函数?
- test() 直接调用
- obj.test() 通过对象调用
- new test() new调用
- test.call/apply(obj) 让test称为obj的方法进行调用,可以让一个函数称为指定任意对象的方法进行调用
<script>
/*
编写程序实现以下功能
*/
function showInfo(age) {
if (age < 18) {
console.log("未成年,再等等!");
} else if (age > 60) {
console.log("算了吧~~~");
} else {
console.log("刚好");
}
}
showInfo(16);
showInfo(20);
showInfo(65);
function f1(){
console.log('f1');
}
var f2=function(){
console.log('f2');
}
f1();
f2();
var obj={};
function test2(obj) {
this.xxx='atguigu';
}
test2.call(obj);
console.log(obj.xxx);
</script>
回调函数
什么是回调函数?
- 你定义的
- 你没有调
- 但最终执行了
常见的回调函数
- dom事件回调函数(与用户交互)
- 定时器回调函数
- ajax请求回调函数(后面讲)(与后台交互)
- 生命周期回调函数(后面讲)
函数分为两种,一种用于储存,一种用于执行
IIFE
全称:Immediately-Invoked Function Expression
作用
- 隐藏实现,一个局部,一个全局的实现
- 不会污染外部(全局)命名空间
- 用他来编码JS模块
<script>
// 匿名函数自调用
(function(){
console.log('......')
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>
注意:
- window.$:把window对象传入这个匿名函数中,并且同时执行这个函数,在页面载入之前就执行
- $是一个函数
- $执行后返回的是一个对象
函数中的this
this是什么?
- 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
- 所有函数内部都有一个变量this
- 他的值是调用函数的当前对象
如何确定this的值?
- test() window
- p.test() p
- new test() 新创建的对象
- p.call(obj) obj
分号问题
在以下2种情况下不加分号会有问题
- 小括号开头的前一条语句
- 方括号开头的前一条语句
var a=3
;(function(){
})()
错误理解:
var a=3(function(){})();
3是undefined,无法运行
var b=4
;[1,3].forEach(function(){
})
错误理解:
var b=4[3].forEach(function(){
})
原型(prototype)
1.函数的prototype属性
每个函数都有一个prototype属性,他默认指向一个object空对象(即:原型对象)
原型对象中有一个属性constructor,它指向函数对象
2.给原型对象添加属性(一般都是方法)——>用于实例对象
作用:函数的所有实例对象自动拥有原型中的属性(方法)
上图,表明对象能找到对象原型,对象原型能找到对象,两者是相互的
<script>
console.log(Date.prototype,typeof Date.prototype);
// {constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …}
// object
function Fun(){
}
Fun.prototype.test=function(){
console.log('test()');
}
console.log(Fun.prototype);//默认指向一个空对象(没有我们的属性)
// {test: ƒ, constructor: ƒ}
// 当我们特地加上才会有我们的属性
// 原型对象中有constructor,他指向函数对象
console.log(Date.prototype.constructor===Date);//true
console.log(Fun.prototype.constructor===Fun);//true
</script>
显式原型与隐式原型
每个函数function都有一个prototype,即显式原型(属性)
每个实例对象都有一个__proto__,可以称为隐式原型(属性)
对象的隐式原型的值为其对应的显式原型的值
function FN(){
}
console.log(FN.prototype);
var fn=new FN();
console.log(fn.__proto__);
console.log(FN.prototype===fn.__proto__);
FN.prototype.test=function(){
console.log('test()');
}
fn.test();
内存结构图(下方)
总结:
函数的prototype属性:在定义函数时自动添加的,默认值是一个空Object对象
对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
程序员能直接操作显式原型,但不能直接操作隐式原型(ES6之前)
原型链
访问一个对象的属性时
- 先在自身属性中查找,找到返回
- 如果没有,再沿着__proto__这条链向上查找,找到返回
- 如果最终没有找到,返回undefined
别名:隐式原型链
作用:查找对象的属性,不是查找对象的变量
function FN(){
this.test1=function(){
console.log("test1()");
}
}
FN.prototype.test2=function(){
console.log("test2()");
}
var FN=new FN()
fn.test1()
fn.test2()
console.log(fn.toString);
fn.test3()
var o1=new Object();
var o2={};
图解:
补充:
函数的显式原型指向的对象默认是空Object实例对象(但Object不满足)
console.log(FN.prototype instanceof Object);//true
console.log(Object.prototype instanceof Object);//false
console.log(Function instanceof Object);//true
所有函数都是Function的实例(包括Function)
console.log(Function.__proto__===Function.prototype);//true
Object的原型对象是原型链尽头
console.log(Object.prototype.__proto__);//null
属性问题
- 读取对象的属性值时,会自动找到原型链中查找
- 设置对象属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
- 方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
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);//xxx
console.log(fn2.a);//yyy
instanceof
表达式:A instanceof B
(A为实例对象,B为构造函数)就是判断A是否为B的实例
如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
Function是通过new自己产生的实例
案例一:
function Foo(){}
var f1=new Foo();
console.log(f1 instanceof Foo);//true
console.log(f1 instanceof Object);//true
案例二:
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);//true
}
面试题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);
面试题2
function F(){}
Object.prototype.a=function(){
console.log('a()');
}
Function.prototype.b=function(){
console.log('b()');
}
var f=new F()
f.a()//a()
// f.b()endefined
F.a()//a()
F.b()//b()
console.log(f);
console.log(Object.prototype);
console.log(Function.prototype);
变量提升与函数提升
引入:为什么输出为undefined?
var a=3
function fn(){
console.log(a);
var a=4;
}
fn()
答:实际运行时,函数体先在内部创建了a,因此在函数中找到的是当前的a,代码如下
var a=3
function fn(){
var a;
console.log(a);
a=4;
}
fn()
1.变量声明提升
- 通过var定义(声明)的变量,在定义语句之前就可以访问到
- 值:undefined
2.函数声明提升
- 通过function声明的函数,在之前就可以直接调用
- 值:函数调用(对象)
console.log(b);//undefined
fn2();//可调用 函数提升
fn3()//不能 变量提升
var b=3;
function fn2(){
console.log('fn2()');
}
var fn3=function(){
console.log('fn3()');
}
3.问题:两者是如何产生的?
答:如上代码
执行上下文
1.代码分类
- 全局代码
- 函数(局部)代码
2.全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文(虚拟的,在栈中)
- 对全局数据进行预处理
- var定义的全局变量==>undefined,添加为window的属性
- function声明的全局函数==>赋值(fun),添加为window的方法
- this==>赋值(window)
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
3.函数执行上下文
- 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象
- 对局部函数进行预处理
- 形参变量==>赋值(实参)==>添加为执行上下文的属性
- arguments==>赋值(实参列表),添加为执行上下文的属性
- var定义的局部变量==>undefined,添加为执行上下文的属性
- function声明的函数==>赋值(fun),添加为执行上下文的方法
- this==>赋值(调用函数的对象)
- 开始执行函数体代码
function fn(a1){
console.log(a1);//3
console.log(a2);//undefined
a3()
console.log(this);//window
console.log(arguments);//伪数组
var a2=3
function a3(){
console.log('a3');
}
}
fn(2,3)