js高级之函数高级部分
基于尚硅谷的尚硅谷JavaScript高级教程提供笔记撰写,加入一些个人理解
原型与原型链
prototype
: 显式原型属性,它默认指向一个Object空对象(即称为: 原型对象)- 原型对象中有一个属性constructor, 它指向函数对象
给原型对象添加属性(一般都是方法)
作用: 函数的所有实例对象自动拥有原型中的属性(方法)
// 每个函数都有一个prototype属性, 它默认指向一个对象(即称为: 原型对象)
console.log(Date.prototype, typeof Date.prototype)
function fun() { }
console.log(fun.prototype);// 默认指向一个Object空对象(没有我们的属性)
//原型对象中有一个属性constructor, 它指向函数对象
console.log(Date.prototype.constructor === Date)// true
console.log(fun.prototype.constructor === fun)// true
//给原型对象添加属性(一般都是方法)
fun.prototype.test = function () {
console.log('test()');
}
//创建一个实例
var f = new fun()
f.test() // 可以执行
所有实例对象都有一个特别的属性
__proto__
: 隐式原型属性
显式原型与隐式原型的关系
- 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
- 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
- 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
- 原型对象即为当前实例对象的父对象
- 对象的隐式原型的值为其对应构造函数的显式原型的值
function Fn() { // 内部语句:this.prototype={}
}
// 每个函数function都有一个prototype,即显式原型
console.log(Fn.prototype)
// 每个实例对象都有一个__proto__,可称为隐式原型
var fn = new Fn() // 内部语句:this.__proto__ = Fn.prototype
console.log(fn.__proto__)
// 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype === fn.__proto__)// true
//给原型添加方法
Fn.prototype.test = function () {
console.log('test()')
}
fn.test()
原型链
基础理解
- 所有的实例对象都有__proto__属性, 它指向的就是原型对象
- 这样通过__proto__属性就形成了一个链的结构---->原型链
- 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
- 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作
访问一个对象的属性时
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着__proto__这条链向上查找, 找到返回
- 如果最终(Object原型对象)没找到, 返回undefined
- 别名: 隐式原型链
- 作用: 查找对象的属性(方法)
构造函数/原型/实体对象的关系(图解)
实例对象的隐式原型指向构造函数的显示原型
构造函数/原型/实体对象的关系2(图解)
函数的显示原型指向的对象默认是Object实例对象(但Object不满足)
console.log(Fn.prototype instanceof Object);// true
console.log(Object.prototype instanceof Object);// false
console.log(Function.prototype instanceof Object);// true
函数实际上是Function的实例对象(包括Function本身),所以函数既有隐式原型也有显示原型
所有函数的隐式原型都指向Function的显示原型
function fun1() { }
function fun2() { }
console.log(fun1.__proto__ === Function.prototype);// true
console.log(fun2.__proto__ === Function.prototype);// true
console.log(fun2.__proto__ === fun1.__proto__);// true
Function本身的隐式原型也指向本身的显示原型
console.log(Function.prototype === Function.__proto__);// true
Object的原型对象是原型链的尽头
console.log(Object.prototype.__proto__)// null
原型链属性问题
- 读取对象的属性值时: 会自动到原型链中查找
- 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
- 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name;
}
Person.prototype.sex = '男';
var p1 = new Person('Tom', 12)
p1.setName('Jack')
console.log(p1.name, p1.age, p1.sex)// Jack 12 男
p1.sex = '女'
console.log(p1.name, p1.age, p1.sex)// Jack 12 女
var p2 = new Person('Bob', 23)
console.log(p2.name, p2.age, p2.sex)// Bob 23 男
instanceof
表达式: A instanceof B
如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
Function是通过new自己产生的实例
案例一
如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
这里很简单可以看出,Foo构造函数的显示原型与实例f1,f2的隐式原型指向同一个对象,所以instanceof判断为true
function Foo() { }
var f1 = new Foo();
console.log(f1 instanceof Foo);// true
console.log(f1 instanceof Object);// true
案例二
//Object函数是function的实例,所以有隐式原型
console.log(Object instanceof Function)// true
console.log(Object instanceof Object)// true
console.log(Function instanceof Object)// true
//Function是通过new自己产生的实例
console.log(Function instanceof Function)// true
function Foo() { }
console.log(Object instanceof Foo);// false
面试题一
注意构造函数的显示原型指向的对象发生了变化
var A = function () {
}
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)// 1 undefined 2 3
面试题二
建议自己画图,画图比较清晰
注意点:找原型链是从隐式原型开始
var F = function () { };
Object.prototype.a = function () {
console.log('a()')
};
Function.prototype.b = function () {
console.log('b()')
};
var f = new F();
f.a() // a() f.__proto__===F.prototype,F.prototype.__proto__===Object.prototype
// f.b() // b is not a function
F.a() // a() F.__proto__===Function.prototype,Function.prototype.__proto__===Object.prototype
F.b() // b() F.__proto__===Function.prototype
执行上下文与执行上下文栈
- 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
- 执行上下文栈: 用来管理产生的多个执行上下文
变量提升与函数提升
- 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)(使用var关键字定义,es6的let,const不会变量提升,现在开发很少使用var关键字,变量一般用let,对象、数组、函数使用const)
- 函数提升: 在函数定义语句之前, 就可以执行该函数
- 先有变量提升, 再有函数提升
var a = 4
function fn() {
console.log(a)
var a = 5
}
fn()// undefined
console.log(a1) //可以访问, 但值是undefined
a2() // 可以直接调用
a3()// 此处遵循变量提升,不能调用
var a1 = 3
function a2() {
console.log('a2()')
}
var a3 = function () {
console.log('a3()');
}
执行上下文理解
代码分类(位置)
- 全局代码
- 函数代码
全局执行上下文
在执行全局代码前将window确定为全局执行上下文
对全局数据进行预处理
- var定义的全局变量==>undefined, 添加为window的属性
- function声明的全局函数==>赋值(fun), 添加为window的方法
- this==>赋值(window)
- 开始执行全局代码
console.log(a1, window.a1);//undefined undefined
a2()//a2()
window.a2()//a2()
console.log(this);
var a1 = 3
function a2() {
console.log('a2()');
}
函数执行上下文
在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
对局部数据进行预处理
- 形参变量>赋值(实参)>添加为执行上下文的属性
- arguments==>赋值(实参列表), 添加为执行上下文的属性
- var定义的局部变量==>undefined, 添加为执行上下文的属性
- function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
- this==>赋值(调用函数的对象)
- 开始执行函数体代码
function fn(a1) {
console.log(a1);// 2
console.log(a2);// undefined
a3()// a3()
console.log(this);// window
console.log(arguments);// 伪数组(2,3)
var a2 = 3
function a3() {
console.log('a3()');
}
}
fn(2, 3)
分类
全局: window
函数: 对程序员来说是透明的
生命周期
全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
函数 : 调用函数时产生, 函数执行完时死亡
执行上下文创建和初始化的过程
全局
- 在全局代码执行前最先创建一个全局执行上下文(window)
- 收集一些全局变量, 并初始化
- 将这些变量设置为window的属性
函数
- 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
- 收集一些局部变量, 并初始化
- 将这些变量设置为执行上下文的属性
执行上下文栈理解
- 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
- 在函数执行上下文创建后, 将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后, 栈中只剩下window
//1. 进入全局执行上下文
var a = 10
var bar = function (x) {
var b = 5
foo(x + b) //3. 进入foo执行上下文
}
var foo = function (y) {
var c = 5
console.log(a + c + y)
}
bar(10) //2. 进入bar函数执行上下文
面试题一
依次输出什么?
整个过程中产生了几个执行上下文? 5个
console.log('gb: '+ i)// undefined
var i = 1
foo(1);// fb:1 fb:2 fb:3 fe:3 fe:2 fe:1
function foo(i) {
if (i == 4) {
return;
}
console.log('fb:' + i);
foo(i + 1);// 递归调用:在函数内部调用自己。每一次调用执行并没有执行完,直到i=4结束递归,重新执行后续代码
console.log('fe:' + i);
}
console.log('ge: ' + i)// ge:1
面试题二
/*
测试题1: 先预处理变量, 后预处理函数
*/
function a() { }
var a;
console.log(typeof a)// function
/*
测试题2: 变量预处理, in操作符
*/
if (!(b in window)) {
var b = 1;
}
console.log(b)// undefined
/*
测试题3: 预处理, 顺序执行
*/
var c = 1
function c(c) {
console.log(c)
var c = 3
}
c(2)// c is not a function
作用域与作用域链
主要是概念类的东西,也很简单,就两个一起讲
理解
作用域: 一块代码区域, 在编码时就确定了, 不会再变化
分类
全局作用域
函数作用域
js没有块作用域(在ES6之前)- 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
作用
- 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
- 作用域链: 查找变量
区别作用域与执行上下文
这里的全局上下文环境中的d应该是b
- 区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
- 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
- 区别2
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会自动被释放
- 联系
- 上下文环境(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
面试题一
/*
问题: 结果输出多少? 10
*/
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
f();
}
show(fn);
面试题二
/*
说说它们的输出情况
*/
var fn = function () {
console.log(fn)
}
fn()
/*
function () {
console.log(fn)
}
*/
var obj = {
fn2: function () {
console.log(fn2)
// 答案:输出结果 报错
// obj对象里的方法fn2,要输出fn2,开始找这个fn2,自己所在的作用域没找到,跑外面继续找
// 跑到全局作用域没找到,输出结果当然报错
// 为什么直接输出fn2却找不到obj里那个fn2呢,因为fn2是属于obj的一个属性/方法,要访问到它就得obj.fn2
// 要输出fn2函数体的话得这么写 console.log(this.fn2)
}
}
obj.fn2()
/*
fn2 is not defined
*/
闭包
闭包引入
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
<script type="text/javascript">
var btns = document.getElementsByTagName('button')
//遍历加监听
// btns是一个伪数组,btns.length需要不断计算,可以提前保存,加快执行效率
for (var i = 0, length = btns.length; i < length; i++) {
/*
这里其实可以将var改成let就可以解决,但let是es6语法,我们js高级只涉及es5
*/
var btn = btns[i]
btn.onclick = function () {
alert('第' + (i + 1) + '个按钮')// 无论哪个按钮都输出“第4个按钮”
}
}
</script>
for (var i = 0, length = btns.length; i < length; i++) {
var btn = btns[i]
btn.index = i// 解决办法 保存下标
btn.onclick = function () {
alert('第' + (this.index + 1) + '个按钮')
}
}
// 利用闭包解决
for (var i = 0, length = btns.length; i < length; i++) {
(function (i) {
var btn = btns[i]
btn.index = i
btn.onclick = function () {
alert('第' + (this.index + 1) + '个按钮')
}
})(i)
}
什么是闭包
如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
闭包到底是什么?
使用chrome调试查看
- 理解一: 闭包是嵌套的内部函数(绝大部分人)
- 理解二: 包含被引用变量(函数)的对象(极少数人)
- 注意: 闭包存在于嵌套的内部函数中
产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数(执行)的数据(变量/函数)
function fn1() {
var a = 3
function fn2() {
console.log(a)// fn2引用了fn1的a,因此形成了闭包
}
fn2()// 笔者目前是2022年,必须调用内部函数才能在chrome中看到闭包
}
fn1()
常用闭包
将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a);
}
return fn2
}
var f = fn1()
f()// 3
f()// 4
//f相当于fn2的实例
将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay('Hello', 2000)
闭包的作用
- 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
function fun1() {
var a = 3;//此处闭包已经产生
function fun2() {
a++; //引用外部函数的变量--->产生闭包
console.log(a);
}
return fun2;
}
var f = fun1(); //由于f引用着内部的函数-->内部函数以及闭包都没有成为垃圾对象
f(); //间接操作了函数内部的局部变量
f();
f=null //此时闭包对象死亡
闭包的生命周期
产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
死亡: 在嵌套的内部函数成为垃圾对象时
闭包的应用
JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包信n个方法的对象或函数
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
写法一
<script type="text/javascript" src="05_coolModule.js"></script>
<script type="text/javascript">
var module = myModule()
module.doSomething()
module.doOtherthing()
</script>
function myModule() {
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething ' + msg.toUpperCase());
}
function doOtherthing() {
console.log('doOtherthing ' + msg.toLowerCase());
}
//向外暴露
return {
doSomething,
doOtherthing
}
}
写法二
<script type="text/javascript" src="05_coolModule2.js"></script>
<script type="text/javascript">
myModule2.doSomething()
myModule2.doOtherthing()
</script>
(function (window) {
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething ' + msg.toUpperCase());
}
function doOtherthing() {
console.log('doOtherthing ' + msg.toLowerCase());
}
//向外暴露
window.myModule2 = {
doSomething,
doOtherthing
}
})(window)//方便代码打包压缩
闭包的缺点
缺点
- 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
- 容易造成内存泄露(详见补充说明)
解决
- 能不用闭包就不用
- 及时释放
function fn1() {
var arr = new Array(100000)// 占用空间大
function fn2() {
console.log(arr.length);
}
return fn2
}
var f = fn1()
f()
f = null//解决办法,让内部函数成为垃圾对象-->回收闭包
面试题一
注意this的指向
//代码片段一
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;//这里没有闭包
};
}
};
console.log(object.getNameFunc()()); //The Window
//代码片段二
var name2 = "The Window";
var object2 = {
name2: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name2;
};
}
};
console.log(object2.getNameFunc()()); //My Object
面试题二
注意是否使用了新产生的闭包
function fun(n, o) {
console.log(o)
return {
fun: function (m) {
return fun(m, n)
}
}
}
var a = fun(0)// undefined
a.fun(1)// 0
a.fun(2)// 0
a.fun(3)// 0
var b = fun(0).fun(1).fun(2).fun(3) //undefined 0 1 2
var c = fun(0).fun(1)// undefined 0
c.fun(2)// 1
c.fun(3)// 1
补充
内存溢出与内存泄露
内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误(加根内存条(bushi))
var obj = {}
for (var i = 0; i < 100000; i++) {
obj[i] = new Array(10000000)
}
console.log('------')
内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
常见的内存泄露
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
// 意外的全局变量
function fn() {
a = [] //不小心没有var定义
}
fn()
// 没有及时清理的计时器
setInterval(function () {
console.log('----')
}, 1000)
js高级之函数高级部分的更多相关文章
- 1、Golang基础--Go简介、环境搭建、变量、常量与iota、函数与函数高级
1 Go语言介绍 1 golang-->Go--->谷歌公司 2009年 golang:指go语言,指的go的sdk goland:软件,ide:集成开发环境 Java写的 2 Go是静态 ...
- python高级之函数
python高级之函数 本节内容 函数的介绍 函数的创建 函数参数及返回值 LEGB作用域 特殊函数 函数式编程 1.函数的介绍 为什么要有函数?因为在平时写代码时,如果没有函数的话,那么将会出现很多 ...
- 第一篇:python高级之函数
python高级之函数 python高级之函数 本节内容 函数的介绍 函数的创建 函数参数及返回值 LEGB作用域 特殊函数 函数式编程 1.函数的介绍 为什么要有函数?因为在平时写代码时,如果没 ...
- 在MySQL中实现Rank高级排名函数【转】
MySQL中没有Rank排名函数,当我们需要查询排名时,只能使用MySQL数据库中的基本查询语句来查询普通排名.尽管如此,可不要小瞧基础而简单的查询语句,我们可以利用其来达到Rank函数一样的高级排名 ...
- [Hive_11] Hive 的高级聚合函数
0. 说明 Hive 的高级聚合函数 union all | grouping sets | cube | rollup pv //page view 页面访问量 uv //user view 访问人 ...
- Oracle 高级排序函数 和 高级分组函数
高级排序函数: [ ROW_NUMBER()| RANK() | DENSE_RANK ] OVER (partition by xx order by xx) 1.row_number() 连续且递 ...
- 使用Vue.js制作仿Metronic高级表格(一)静态设计
Metronic高级表格是Metonic框架中自行实现的表格,其底层是Datatables.本教程将主要使用Vue实现交互部分,使用Bootstrap做样式库.jQuery做部分用户交互(弹窗). 使 ...
- Scala学习——函数高级操作
scala函数高级操作 一.字符串高级操作 多行字符串和插值 package top.ruandb.scala.Course06 object StringApp { def main(args: A ...
- js自执行函数的常见写法
js自执行函数的常见写法 2016-12-20 20:02:26 1.关于自执行函数 1.1 写自执行函数的好处:独立的作用域,不会污染全局环境 (function() { })(); 1.2 理解重 ...
随机推荐
- Taurus.MVC 微服务框架 入门开发教程:项目部署:6、微服务应用程序Docker部署实现多开。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 开源地址:https://github.com/cyq1162/Taurus.MVC 本系列第一篇:Tauru ...
- window桌面背景图片
通过修改注册表项: \HKEY_CURRENT_USER\Control Panel\Desktop下的几个值,及可以将我们想要的图片设置成桌面的背景图 TileWallpaper Wallpap ...
- Linux系统编程001--系统IO
1. 文件系统:用来存储.组织.管理文件的一套方式.协议 2. 文件 文件的属性:i-node唯一表示一个文件的存在与否 文件的内容 3. Linux系统如何实现文件的操作? 点击查看代码 硬件层: ...
- 【设计模式】Java设计模式 - 享元模式
Java设计模式 - 享元模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起记录分享自己 ...
- 使用J2EE 登录实例开发
我们先了解下Servlet的生命周期 Servlet部署在容器里,其生命周期由容器管理. 概括为以下几个阶段: 1)容器加载Servlet类. 当第一次有Web客户请求Servlet服务或当Web服务 ...
- SpringBoot Xml转Json对象
一.导入需要的依赖 <dependency> <groupId>maven</groupId> <artifactId>dom4j</artifa ...
- k8s中的ingress使用上层负载均衡进行设置访问
注意:这种情况下需要有个前提条件,也就是ingress-nginx-controller安装后的service是NodePort或者hostNetwork模式,而不能是ClusterIP,因为负载均衡 ...
- Kibana插件
附加的功能在 Kibana 中是以插件的形式提供的.您可以利用 bin/kibana-plugin 命令来管理这些模块.您也可以手动安装这些插件,只需要将这些插件包放到 plugins 目录并解压到新 ...
- 使用docker-compose部署Django项目
先从最基本的功能开始 在一切工作开始前,需要先编辑好三个必要的文件. 第一步,因为应用将要运行在一个满足所有环境依赖的 Docker 容器里面,那么我们可以通过编辑 Dockerfile 文件来指定 ...
- Prometheus与服务发现
这种按需的资源使用方式对于监控系统而言就意味着没有了一个固定的监控目标,所有的监控对象(基础设施.应用.服务)都在动态的变化.对于Prometheus这一类基于Pull模式的监控系统,显然也无法继续使 ...