一、函数定义
二、局部函数预编译 AO
三、全局函数预编译 GO
四、作用域
五、闭包
六:匿名函数
七:对象 --》7.1实例对象 --》 7.2继承
八:包装类
九:克隆
一、函数定义
<!-- 定义 function test() {}
函数声明
函数表达式 var test = function() {}
组成形式
函数名称 test
参数
形参 function one(a,b){ // 形参中如果没有可接收的,都有一个 arguments ,接收所有实参传过来的值}
实参 one(1, 2)
返回值 return
-->
<script>
var a = 1 //window GO
function test() {
var b = 123 // 且在函数内,是 AO ,局部
c = 345 // 未经声明的变量归 window 所有
console.log(window);
console.log(window.b);
}
test()
</script>
二、局部函数预编译 AO
<script>
// 二、局部函数预编译 AO
/*1语法分析 → 2预编译 → 3解释执行*/
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
/*
局部 4 部曲
1.创建 AO 对象 Activation Object(执行期上下文,作用是理解的作用域,函数产生
的执行空间库)
2.找形参和变量声明,将变量和形参名作为 AO 属性名,值为 undefined
相当于 AO{
a : undefined,
b : undefined
}
3.将实参值和形参统一(把实参值传到形参里)
4.在函数体里面找函数声明,值赋予函数体 (所以为什么说 两个相同的函数后一个函数会覆盖前一个函数)
(先看自己的 AO,再看全局的 GO)
*/
function test(a, b) {
console.log(a) // function a
console.log(b) // function b // b 函数是声明的才会提升,赋值则不会
var b = 234
console.log(b); // 234
a = 123
console.log('a: ', a); // 123
function a() {}
var a;
b = 233
var b = function(){}
console.log('a: ', a); // 123
console.log('b: ', b); // 233 // b函数 通过 = 赋值,所以不会变量提升
}
// test(1)
// 局部的AO {
// a: undefined -> 1 -> function a(){} -> 123
// b: undefined -> function b(){} - 234 -> 233 错误的
// b: undefined - 234 -> 233 -》 function b(){} 正确的
// }
</script>
三、全局函数预编译 GO
<script>
// 三、全局函数预编译 GO
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
// 预编译发生在函数执行前一刻 预编译也叫执行期上下文
/*
全局的预编译三部曲:
1、生成了一个 GO 的对象 Global Object(window 就是 GO)
2、找形参和变量声明,将变量和形参名作为 GO 属性名,值为 undefined
3、在函数体里面找函数声明,值赋予函数体 (所以为什么说 两个相同的函数后一个函数会覆盖前一个函数)
*/
// function test(){
// var a = b = 123; // b 没有声明就是赋值了,归 window 所有,就是在 GO 里面预编译
// console.log('a', a)
// console.log('window', window)
// console.log(window.b);
// }
// test();
// console.log(test); // GO 的test
function test(test){
console.log(test); // AO的 function test(){}
var test = 234;
console.log(test); // 234
function test(){}
}
// test(1);
var test = 123;
/*
GO: {
test: undefined -> function test ->
AO: {
test: undefined -> function test -> 234
}
}
*/
</script>
四、作用域
<script>
// 四、作用域
/*
[[scope]]:每个 javascript 函数都是一个对象,对象中有些属性我们可以访问,但有些 不可以,
这些属性仅供 javascript 引擎存取,[[scope]]就是其中一个。[[scope]]指的就 是我们所说的作用域,
其中存储了运行期上下文的集合。
作用域链: [[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我 们把这种链式链接叫做作用域链。
运行期上下文: 当函数在执行的前一刻,会创建一个称为执行期上下文的内部对象。 一个执行期上下文定义了一个函数执行时的环境,
函数每次执行时对应的执行上下 文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,
当函数执 行完毕,执行上下文被销毁。
查找变量:在哪个函数里面查找变量,就从哪个函数作用域链的顶端依次向下查找。
函数类对象,我们能访问 test.name
什么是函数对象 ====> 通过函数调用
function test() {}
test.call()
什么是实例对象 ====> 创建出的实例调用,就是实例对象
Person.prototype.test = function() {}
function Person() {}
const person = new Person()
person.test()
*/
// 作用域 ---》 函数中执行函数会产生作用域链 [[scope]] 就是将 GO AO链起来
function a() {
function b() {
var bb = 234;
aa = 0 // 未经过声明的变量归window所有(全局也没有定义)否则还是不归window所有,当前函数 a1 执行时 scope 中查找GO 上一级有声明,所以会取上一级的值
// var aa = 0
console.log('window', window)
}
var aa = 123
b()
console.log(aa)
}
var glob = 100
// a()
/*
1、当这个文件先要执行时,先创建出执行期上下文全局 Gloab Object,
a defined --> [[scope]] --> 0 GO{
glob: undefined --》 100
a: function a(){}
}
2、当a函数开始执行时,在当前函数内会产生 aAO放在作用域链的最顶端,
a doing --> [[scope]] --> 0 AO{ // 放在作用域链的最顶端
aa: undefined
b: function(){}
}
1 GO{
glob: undefined--》 100
a: function a(){}
}
3、当看到b函数被定义 function b(){} ,会将 a函数执行之后的 scope 地址引用跟 b函数定义时的 scope 地址相关联
b defined --> [[scope]] --> 0 aAO{ // 放在作用域链的最顶端
aa: undefined --> 123 --> 0
b: function(){}
}
1 GO{
glob: undefined--》 100
a: function a(){}
}
4、当a函数开始执行时,在当前函数内会产生 bAO放在作用域链的最顶端,
b doing --> [[scope]] --> 0 bAO {
b: undefined
}
1 aAO{ // 放在作用域链的最顶端
aa: undefined --> 123 --> 0
b: function(){}
}
2 GO{
glob: undefined--》 100
a: function a(){}
}
5、当b函数执行完之后,销毁 b 的执行期上下文,将指向b的地址清除
6、当b函数执行完之后,销毁 a 的执行期上下文,将指向b的地址清除
7、如果重新执行a函数,那么重新定义执行期上下文
*/
// function a1() {
// cc = 0 // 未经过声明的变量归window所有(全局也没有定义)否则还是不归window所有,当前函数 a1 执行时 scope 中查找GO 上一级有声明,所以会取上一级的值
// }
// var cc = 123
// a1()
// console.log(cc)
</script>
五、闭包
<script>
// 五、闭包
// 当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露。 最后可将返回的函数 = null,释放空间
实践角度上闭包的定义:
一、即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
如何解释:每个函数执行都会产生执行期上下文,将每个执行期上下文通过scope链起来,就是产生作用域链
二、在代码中引用了自由变量
参考链接:https://juejin.cn/post/6844903475998900237
/*
一、实现公有变量
二、可以做缓存(存储结构)
三、可以实现封装,属性私有化。
四、模块化开发,防止污染全局变量
闭包的防范:闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生。
*/
// 一、实现公有变量
function add() {
var count = 0
function demo() {
count ++ // 内部函数中访问外部变量,形成闭包,可以打断点查看
console.log(count)
}
return demo
}
var counter = add()
// counter()
// 二、可以做缓存(存储结构)
function fun(){
var count = 1;
function fun2(){//条件1:函数嵌套
//形成条件2:内部函数引用外部函数
console.log(count);
}
fun2()
}
// fun();
// 三、可以实现封装,属性私有化。
/*
// 函数中定义的属性不会被销毁,因为每个函数都有一个原型链,属性跟方法都存在__proto__中,将内部的属性方法保存在外部,
// new 中发生了什么?? 全局搜索: new实现过程 ,答案自然揭晓
// function Deng(name,wife){
// var prepareWife = "xiaozhang";
// this.name = name;
// this.wife = wife;
// this.divorce = function(){
// this.wife = prepareWife;
// }
// this.changePrepareWife = function (target){
// prepareWife = target;
// }
// this.sayPrepare = function (){
// console.log(prepareWife);
// }
// }
// var deng = new Deng("deng","xiaoliu");
// console.log('deng.wife: ', deng.wife);
// deng.divorce()
// console.log('deng.wife: ', deng.wife);
*/
var name = '123456'
function fun1() {
let that = this
var name, age, modulesName;
name = '张三'
age = '10'
modulesName = 'fun1'
var findValue = function () {
console.log(name, age, modulesName);
}
return function () {
findValue()
}
}
fun1()()
function fun2() {
var name, age, modulesName;
name = '李四'
age = '20'
modulesName = 'fun2'
return function () {
console.log(name, age, modulesName);
}
}
fun2()()
// 四、模块化开发,防止污染全局变量
var zing_obj = {
gzm: {
name: '我是哈哈?',
abc: function() {
console.log(this.name)
}
},
www: {
name: '就这个吧?'
}
}
zing_obj.gzm.abc()
// 闭包的防范:闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生。
</script>
六:匿名函数
<script>
// 六:匿名函数
/* */
// 一(function (){}()); //在 W3C 建议使用这一种
// 二(function (){})(); // 浏览器已经报错了,无法支持
(function() {
console.log('11111')
}())
//这是表达式,可以被执行,此时在控制台执行 test 的结果是 undefined,这个函 数的名字就会被放弃
var test = function (){
console.log('这是表达式,声明了变量,用函数接收了');
}()
// 这个执行不了,报错,还是用 W3C写法吧
// (function(){
// console.log('这是函数表达式代扣括号,执行吧');
// })()
// 加了个“正“,在趋势上要把他转换成数字,就是表达式了,既然是表达式就 能被执行,就会放弃名字,此时 console.log (test),就会报错;这就是立即执行函数 同样放了正号,负号,!就会放弃函数名字,转换成表达式;但是*和/不行,&&||前 面放东西也行
+ function(){
console.log('+++++这也是表达式');
}()
let obj = { name: 'zhangsan', age: 12}
console.log( obj.name && obj.age)
console.log( obj.name || obj.age)
if ('undefined') {
console.log('我能打印嘛')
}
</script>
七:对象 --》7.1实例对象 --》 7.2继承
所有实例对象都通过__proto__指向的都是原型对象,可通过constructor指向当前构造函数
<script>
// 原型链该怎么解释: 每个函数都有一个prototype属性,每个属性中都有一个constructor指向函数对象本身,每个实例对象都有一个__proto__,指向他的 prototype,
// 七:对象
/*
// 一、对象
var obj = {} // 字面量创建
var obj1 = new Object() // 构造函数创建
//二、构造函数
Car.prototype.run = function() {
this.height --
console.log('height', this.height);
}
// Car.prototype = {
// constructor: Car,
// run: function() {},
// start: function() {}
// }
function Car() {
this.name = 'BMW'
this.color = 'red'
this.height = 100
// this.run = function() {
// this.height --
// console.log('height', this.height);
// }
}
const car1 = new Car()
console.log('__proto__', car1.__proto__);
console.log('prototype', Car.prototype);
car1.run()
const car2 = new Car()
car2.run()
*/
/*
原型 prototype
定义:原型是 function 对象的一个属性,它定义了构造函数制造出的对象的公共祖
先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
实例对象 __proto__
一、内存浪费?
如果调用两次new就会单独开辟两个空间来存放this.run ,如果创建100个呢,那么就容易内存浪费。
this.run = function() {
this.height --
console.log('height', this.height);
}
二、如何解决?
一般情况下,我们公共的属性定义到构造函数中,公共方法放在原型对象上(prototype)
Car.prototype.run = function() {
this.height --
console.log('height', this.height);
}
三、每个实例对象都有一个 __proto__ 、指向构造函数的 prototype 原型对象,实例对象中的
car1.__proto__ === Car.prototype
四、如果有多个原型对象,改为对象形式,那么constructor就会丢失,这时候就需要我们修改constructor
*/
Car.prototype = {
constructor: Car,
run: function() {},
start: function() {}
}
function Car() {
console.log('478-this', this);
}
const car1 = new Car()
console.log(car1.__proto__); // constructor丢失
console.log(Car.prototype); // constructor丢失
// 三、原型链
/*
Person.prototype.run = function() {
that = this
}
Object.prototype.age = 15
// Person.prototype.age = 14
function Person() {}
const person1 = new Person()
// person1.age = 13
person1.run()
console.log('age', person1.age);
console.log('this指向实例对象', person1 === that);
// 原型链: 访问age,实例对象上没有,向Person.prototype中查找,还没有,向Object.prototype中查找,如果都没有就没有
// 原型链 ,最后直到访问 null,就是最顶级的。Person.prototype指向Person原型对象,在向上访问到__proto__指向Object原型对象,在向上访问为null
console.log('person.proto指向Person原型对象', person1.__proto__);
console.log('Person.prototype指向Person原型对象', Person.prototype);
console.log('Person.prototype.__proto__指向Object原型对象', Person.prototype.__proto__);
console.log('Object原型对象.__proto__指向 null 万物皆是空 null', Person.prototype.__proto__.__proto__);
*/
// 四、es5中使用实例对象继承、es6中使用class 类继承, 如何实现?
/*
// 1、如何让孩子继承父亲的属性
// 2、如何让孩子继承父亲的方法,(存在问题?) 自己来一遍吧
Father.prototype.run = function() { console.log('都需要运动')}
function Father(name, age) {
this.name = name
this.age = age
}
const father = new Father()
function Son(name, age) {
//1、 继承属性
Father.call(this, name, age)
this.study = '学习'
}
// // 2、继承方法 存在的问题,给孩子添加方法时,父级也会添加,不可取
// Son.prototype = Father.prototype
// Son.prototype.start = function() { console.log('开始运动')}
Son.prototype = father // 相当于 son.prototype = {},重新修改了原型对象,需要执行constructor
Son.prototype.constructor = Son
const son = new Son('孩子', 10)
console.log('实例对象', son);
console.log('原型对象', Son.prototype);
son.run()
console.log(Father.prototype);
console.log(Son.prototype.constructor);
*/
/*
// 第一种
Person.prototype.name = '小一'
function Person() {
}
Son.prototype = Person.prototype
function Son() {}
Person.prototype.name = '小二'
const son = new Son()
console.log('son.name: ', son.name); // 观察Person.prototype 赋值之后打印的name
Person.prototype.name = '小三'
*/
/*
// 第二种 引用赋值
Person.prototype.name = '小一' //let obj = { name: 'a'}
function Person() {
// this = {
// __proto__ : Person.prototype // 隐士调用
// }
}
Son.prototype = Person.prototype // let obj1 = obj
function Son() {}
Person.prototype = { // obj = {name: 'b'}
name: '小二'
}
const son = new Son()
console.log('son.name: ', son.name); // 会影响原来 Son。prototype 指向的name 吗?
let obj = { name: 'a'}
let obj1 = obj
obj = {name: 'b'}
console.log(obj1.name);
*/
/*
一、...args剩余参数(展开运算符) 返回一个数组,可以使用数组原型中的方法
允许一个表达式在某处展开。展开运算法 在 多个参数(函数调用)、多个元素(用于数组和字面量)和多个变量(用于解构赋值) 地方使用。剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
如果函数的最后一个命名参数以 ... 为前缀,则它会将所有后面剩余的是实参个数包裹成一个数组。
// 例子
function test(a, b, ...args) {
console.log(args)
}
test(1,2,3,4) // [3, 4]
二、arguments对象 返回一个 类数组对象,不具有数据的方法
在函数代码中,使用特殊对象 arguments,开发者无需明确指出参数名,就能访问它们。arguments对象并不是一个数组,是一个类数组对象,在调用时请注意。
arguments 中包含了函数传递的参数、length、和 callee 属性。
length 属性表示的是实参的长度,即调用函数的时候传入的参数个数。
callee 属性则指向的函数自身,我们可以通过它来调用自身函数。
arguments 是一个经典的类数组对象,我们可以通过Function.call 或者 Function.apply 方法来间接调用数组的方法,也可以直接通过 Array.prototype.slice 或 Array.prototype.splice 等方法把类数组对象转换成真正的数组。
*/
// new实现过程
/*
function myNew(_constructor, ...args) {
debugger
// 1. 创建一个空对象
const obj = {};
// 2. 将obj的_proto_属性指向构造函数的原型对象
obj.__proto__ = _constructor.prototype;
// 3.将_constructor执行的上下文this绑定到obj上,并执行
const result = _constructor.apply(obj, args);
//4. 如果构造函数返回的是对象,则使用构造函数执行的结果。否则,返回新创建的对象
return result instanceof Object ? result : obj; // 如果使用自己函数的对象,那么原型中的函数将无法使用
}
function Person(name, age){
this.name = name;
this.age = age;
return { name: 123}
}
Person.prototype.sayHello = function(){
console.log("hello" + this.name)
}
const person1 = myNew(Person, 'Tom', 20)
const person2 = new Person('Tom', 20)
console.log(person1) //Person {name: "Tom", age: 20}
console.log(person2) //Person {name: "Tom", age: 20}
// person1.sayHello() //helloTom
// person2.sayHello() //helloTom
function myNew1(_construtor, ...args) {
}
*/
// 注意
/*
Object.create() //在括号里面只能放 null 或者 Object,其余会报错
// 练习题 执行 test() 跟 new test() 会返回什么结果。
var a = 5
function test() {
a = 0
console.log(a)
console.log(this.a)
var a
console.log(a)
}
// 分析:只要new 就会隐士 var obj = {} obj.__proto__ = test.prototype return this,访问 obj.a = undefined
*/
</script>
八:包装类
<script>
// 八、包装类 //https://blog.csdn.net/qq_42284274/article/details/127143590
/*
JS为我们提供了三个包装类:
String() :将基本数据类型字符串,转换为String对象。
Number() :将基本数据类型的数字,转换为Number对象。
Boolean() :将基本数据类型的布尔值,转换为Boolean对象。
*/
// //通过上面这这三个包装类,我们可以将基本数据类型的数据转换为对象。
// var num = new Number(3);
// var str = new String("he11o");
// var bool = new Boolean(true);
// console.log(typeof num); //打印结果: object
//PS:我们在实际应用中不会使用基本数据类型的对象。如果使用基本数据类型的对象,在做一些比较时可能会带来一些不可预期的结果。
// var str = 123;
// // str = str.toString(); //将number类型转换为string 类型
// str.hello = "千古壹号"; //添加属性
// console.log(typeof str); //打印结果: string
// console.log(str.he11o); //打印结果: undefined
</script>
九:克隆
<script>
function deepCopy(obj, cache = []) {
// 如果obj是不可变值,只返回
if (obj === null || typeof obj !== 'object') {
return obj
}
// 如果obj被击中,则其为圆形结构
const hit = find(cache, c => c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// 首先将副本放入缓存,因为我们希望以递归方式引用它
// deepCopy
cache.push({original: obj, copy})
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
/*
为什么有深拷贝以及前拷贝??
这里主要区分原始值和引用值的拷贝,原始值的拷贝,拷贝的是整个原始值,而引用值的拷贝,拷贝的是地址,如果是浅拷贝,一旦obj对象中含有引用值。拷贝之后,如果对obj1进行改变,则obj的值也会发生改变。
参考地址:https://www.cnblogs.com/kkw-15919880007/p/14666416.html
深拷贝封装考虑三个点:
1,是否有长度,没有直接return
2,对象属性中是否具有自己添加的指定的属性 Person.prototype.name = '123',返回true,toString 就返回false
3,判断是否为 object,if 存在( 继续判断是否为 Object,Array,Function 新增属性) else 将值直接复制到 对象或者数组中
4、
*/
function clone(obj, obj_clone = {}){
// 判断被克隆的对象是不是一个空对象,如果是空对象就直接return,不在继续往下执行
if(Object.keys(obj).length == 0) return obj_clone;
// 遍历对象
for(var key in obj){
// 判断当前遍历到的属性是不是对象的,(不是原型上的,原型上的不克隆)
if(obj.hasOwnProperty(key)){
// 判断obj当前属性是原始类型还是引用类型
// 使用typeof 可以简单区分原始类型和引用类型
if(typeof obj[key] == 'function' || typeof obj[key] == 'object'){
// 调用toString方法
toString = Object.prototype.toString;
if(toString.call(obj[key]) == '[object Object]'){
obj_clone[key] = {};
} else if(toString.call(obj[key]) == '[object Array]') {
obj_clone[key] = [];
} else if(toString.call(obj[key]) == '[object Function]'){
obj_clone[key] = new Function();
}
// 递归,当有数组或者对象的时候继续往里面深入,重复调用当前的函数
// 第一种方法
// callee是arguments对象的一个属性,指向arguments对象的函数这个函数就是clone(clone=arguments.callee)
// arguments.callee(obj[key], obj_clone[key]);
// 第二种方法
clone(obj[key], obj_clone[key]);
} else { // 原始数据类型直接赋值
obj_clone[key] = obj[key];
}
}
}
return obj_clone;
}
var obj = {
original : 2,
arr : [1,2,3],
fun : function(){
return 'fun';
},
obj_son : {
original_son : 3,
obj_son_son : {}
}
}
let newobj = clone(obj);
console.log(newobj === obj); // false
</script>