基础知识一:理解表达式和语句
- 表达式计算出一个值但不作任何操作,不改变程序的运行状态。
- 语句会改变程序的运行状态。
var num=10; //表达式
//for循环语句
for(var i=0;i<num;i++){
num+=i
}
基础知识二:定时器的另一种调用方式
function move(){console.log('move')}
setTimeout(move,100)
基础知识三:在表单控件input上onchange的触发条件
- 当input捕获到焦点后,系统储存当前值
- 当input焦点离开后,判断当前值与之前存储的值是否不等,如果为true则触发onchange事件。
- 非IE可以回车触发。如果你敲回车的时候,前后两次的值不相等才会触发
- 通过DOM对象对input赋值(就算是和之前value不相等的值)不会发生onchange事件(但可以通过手动赋值触发事件)
- 页面上有一排单选按钮,只操作一个单选按钮的开关(改变其状态)时会触发onchange,但是如果单击其他单选按钮时而导致这个单选按钮状态的改变,后者不会触发onchange事件
基础知识四:扫盲parseFloat()、parseInt()
- parseFloat()函数可解析一个字符串,并返回一个浮点数,可以解析整数和浮点数
- 语法:parseFloat(string),参数是必须的,表示要被解析的字符串,默认为10进制转换
- 注意事项:开头和结尾的空格是允许的;如果字符串的第一个字符不能被转换为数字,那么 parseFloat() 会返回 NaN;如果只想解析数字的整数部分,请使用 parseInt() 方法;只有字符串中的第一个数字会被返回。
- parseInt()只解析整数
parseInt('.1') //NaN
parseInt('0.1') //0
parseFloat('0.1') //0.1
parseFloat('$0.1') //NaN 数字不能以‘$’开始
<script type="text/javascript">
document.write(parseFloat("10")) //10
document.write(parseFloat("10.00")) //10
document.write(parseFloat("10.33")) //10.33
document.write(parseFloat("34 45 66")) //34
document.write(parseFloat(" 60 ")) //60
document.write(parseFloat("40 years")) //40
document.write(parseFloat("He was 40")) //NaN
</script>
依次输出:
10
10
10.33
34
60
40
NaN
基础知识五:扫盲toFixed()、toExponential()、toPrecision()
- 在做数字到字符串的转换中:
- toFixed()方法四舍五入取指定位数的小数点,当其中参数为0时表示不留小数点
- toFixed()根据小数点后的指定位数将数字转换为字符串,它从不用指数法。
- toExponential()使用指数记数法将数字转换为指数形式的字符串,其中小数点前只有一位,小数点后的位数由参数指定
123456.789.toExponential(6)
==>"1.234568e+5"
- toPrecision()根据指定的的有效数字位数将数字转为字符串。如果有效数字的位数少于数字整数部分的位数,则转换为指数形式。
123456.789.toPrecision(1)
==>"1e+5"
123456.789.toPrecision(2)
===>"1.2e+5"
123456.789.toPrecision(6)
===>"123457"
- parseFloat遇到精度问题可与toFixed结合使用
基础知识六:js标识符的写法
- js标识符必须以字母、下划线(_)、美元符($)开始,不能以数字开头
**基础知识七:扫盲isFinite()
**
- 看返回值,如果 number 是有限数字(或可转换为有限数字),那么返回 true。否则,如果 number 是 NaN(非数字),或者是正、负无穷大的数,则返回 false。
基础知识八:扫盲encodeURI()、encodeURIComponent()
- encodeURI()是Javascript中真正用来对URL编码的函数。 编码整个url地址,但对特殊含义的符号
"; / ? : @ & = + $ , #",也不进行编码。对应的解码函数是:decodeURI()。 - encodeURIComponent() 能编码
"; / ? : @ & = + $ , #"这些特殊字符。对应的解码函数是decodeURIComponent()。 - 假如要传递带&符号的网址,用encodeURIComponent()
基础知识九:JavaScript的两种数据类型
- javascript的数据分为两类:原始类型(primitive type)和对象类型(object type)。也可以分为可变(mutable)类型和不可变(immutable)类型
- 例如对象和数组是可变类型
- 数字,布尔值,null,undefined属于不可变类型
基础知识十:理论知识大杂烩
- 浮点型直接量和整型直接量的区分
整型直接量
十进制 10
十六进制 0xff
八进制 0377
浮点型直接量
3.14
.333
6.02e-23
- 基于无穷大值的加减乘除结果还是无穷大值(保留正负号)
- 二进制浮点数表示法并
不能精确到类似0.1这样简单的数字
0.3-0.2=0.09999999999999998
null,undefined,'',0,-0会转换为falsetypeof undefined ==>undefined- 当使用var声明一个变量时,创建的这个属性是不可配置的
var x=2;
delete x;===> false
- 数组的最后以逗号结束时,自动忽略逗号
var c=[1,2,]
==>[1,2]
-
如果函数使用return语句给出一个返回值,那么这个返回值就是整个调用表达式的值,否则,调用表达式的值就是undefined
-
void忽略照常计算的操作数结果并返回undefined,例如
<a href="javascript:void window.open()">打开一个新窗口</a> -
通过
x!==x来判断x是否为NaN,只有当x为NaN的时候,这个表达式才为true -
三目运算符的经典使用场景是判断一个变量是否有定义(并拥有一个有意义的真值),如果有定义则使用它,如果无定义则使用一个默认值。例如
username?username:'lth' -
break是跳转到循环或者其他语句的结束
-
continue是终止本次循环的执行并开始下一循环的开始
-
return语句让解释器跳出函数体的执行,并提供本次调用的返回值
-
throw语句触发或者抛出一个异常
-
对象最常见的方法是创建(creat)、设置(set)、查找(query)、删除(delete)、检测(test)、枚举(enumerate)
-
undefined没有length属性
-
&&的链式使用,例如
var len=book&book.title&book.title.length -
Object.prototype的方法结构是{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} -
delete运算符只能删除自有属性,不能删除继承属性 delete不能删除全局函数
-
通过直接赋值数组的了length来截取数组
-
如果一个函数不包含return语句,那它就执行函数体中的每条语句,并返回undefined
-
宁愿程序在传入非法值时报错,也不愿意非法值导致程序在执行时报错
**基础知识十一:扫盲String.search()、stringobj.match()、String.replace()
**
'hello gril'.search(/he/ig) ==>0
'hello gril'.search(/llo/ig) ===>2
'te: 2,3,4'.replace(/\d+/g,'$') ==> 'te: $,$,$'
基础知识十二:利用toString()实现扁平化数组
[1,2].toString()
==>"1,2"
[1,2].join()
==>"1,2"
[1,2].join("").split('')
==> ["1", "2"]
-** stringObject.match( regExp )**
match()方法的返回值为Array类型,其返回数组的成员取决于指定的正则表达式模式是否设有全局标志g。
1、如果参数regExp**没有**全局标志g,则match()函数只查找第一个匹配,并返回包含查找结果的数组,该数组对象包含如下成员:
索引0:存放第一个匹配的子字符串。
属性index:匹配文本在字符串中的起始索引位置。
属性input:整个字符串对象(stringObject)。
在 IE 6 ~ IE 8 中,该数组还具有额外的lastIndex属性,用于存储匹配文本最后一个字符的下一个位置。
2、如果参数regExp**设有**全局标志g,则match()函数会查找所有的匹配,返回的数组不再有index和input属性,其中的数组元素就是所有匹配到的子字符串,形如:
索引0:存放第一个匹配的子字符串(如果存在的话)。
索引1:存放第二个匹配的子字符串(如果存在的话)。
基础知识十三:运算符的结合性原则
w=x-y-z
--->w=((x-y)-z)
x=~-y
w=x=y=z
q=a?b:c?d:e?f:g
--->x=~(-y)
--->w=(x=(y=z)
--->q=a?b:(c?d:(e?f:g))
1+{}
--->"1[object Object]"
2+null
--->0
2+undefined
--->NaN
基础知识十四:扫盲字符串对比之localCompare()方法
-
string.localeCompare(targetString,locales,options);
-
返回值:返回值是一个数字,目前的主流浏览器都返回的是1、0、-1三个值,但是也有其他情况,所以不可以用绝对的值等于1、-1这种去判断返回的结果
-
返回值大于0:说明当前字符串string大于对比字符串targetString
-
返回值小于0:说明当前字符串string小于对比字符串targetString
-
返回值等于0:说明当前字符串string等于对比字符串targetString
ar strList = ['cc', 'ee', 'ca', 'aa'];
strList.sort((a, b) => {
return a.localeCompare(b);
});
console.log(strList);
//["aa", "ca", "cc", "ee"]
'11'.localeCompare('3')
--->-1
基础知识十五:扫盲delete
h=[1,2,3]
--->(3) [1, 2, 3]
delete h[0]
--->true
h
--->(3) [empty, 2, 3]
h.length
--->3
结论:
delete删除了这个元素,但删除操作留下了一个‘洞’,实际上并没有修改数组的长度
hh={a:1,u:2}
--->{a: 1, u: 2}
delete hh.x
--->true
hh
--->{u: 2}
'a' in hh
false//这个属性在对象中已经不存在了
结论:delete可以删除对象的属性,但并不是所有的属性都可以删除,一些内置核心和客户端属性是不能删除的。
而且delete不能删除用var声明的变量
- delete运算符只能删除自有属性,不能删除继承属性 delete 不能删除全局函数
基础知识十六:在语句块中声明的变量并不是语句块私有的
{
var x=1;
}
console.log(x) ==>1
if(username ==null){
//如果username是null或者undefined
}
if(!userame){
//如果username是null、undefined、false、0、''、或者NaN
}
(function g(){
var gggg=1;
console.log(gggg)
})()
undefined
gggg
--->
VM14529:1 Uncaught ReferenceError: gggg is not defined at <anonymous>:1:1
基础知识十七:何谓属性的特性(property attribute)?
-
属性特性有:congigurable、enumerable、writable、value
-
结合object.creat()来理解,注意观察第二个参数的使用
-
object.creat()是一个新的对象可以继承一个对象的属性,并且可以自行添加属性。
var parents = {
name : "UW3C",
bron : "2013",
from : "China"
}
//o2不继承任何属性和方法
var o2=Object.creat(null)
//o3和{}和new Object()一样
var o3=Object.creat(Object.prototype)
var child = Object.create(
parents,
{
title : {
value : "技术分享",
},
year : {
value : "2",
}
}
);
var obj = Object.create({},{
"a":{value :1,congigurable :false,enumerable :true,writable:true},
"b":{value :2,congigurable :false,enumerable :true,writable:true},
"c":{value :3,congigurable :false,enumerable :true,writable:true}
});
基础知识十八:扫盲Object.propertyIsEnumerable()
- 只有检测到是自有属性且这个属性的可枚举性为true时才会返回true
只有检测到是自有属性且这个属性的可枚举性为true时才会返回true
基础知识十九:getter和setter存取器属性(accessor property)和数据属性(data property)的区分
- 存储器属性都是成对定义的函数
- 数据属性的四个特性是
value\writable\enumerable\configurable - 存取器属性的四个特性是
get\set\enumerable\configable不具有value和writable
var obj={
data:value,
get accessor_prop(){},
set accessor_prop(value){}
}
当存储器查询存储器属性的值时,
JavaScript调用getter方法(无参数),
这个方法的返回值就是属性存取表达式的值
当程序设置一个存储一个存取器属性的值时,
JavaScript调用setter方法,
将赋值表达式右侧的值当作参数传入setter
只设置getter时为 只读
只设置setter时为 只写
读取只写属性总是返回undefined
- 引申
属性描述符的概念(property descriptor) - 引申
Object.getOwnPropertyDescriptor()方法
Object.getOwnPropertyDescriptor({x:1},'x')
--->
{
value: 1,
writable: true,
enumerable: true,
configurable: true
}
var obj = {
//给a定义一个getter
get a(){
return 2;
}
};
obj.a = 3; //不能修改
console.log(obj.a); //2
//只设一个getter时是只读属性
Object.getOwnPropertyDescriptor(obj,'a')
--->
{
set: undefined,
enumerable: true,
configurable: true,
get: ƒ
}
//读取只写属性setter总是返回undefined
Object.getOwnPropertyDescriptor({},'x')
--->undefined
Object.getOwnPropertyDescriptor({},'toString')
--->undefined
对于继承和不存在的属性返回undefined
只能得到自有的属性的描述符
- 引申
Object.defineProperty()方法
Object.defineProperty{o,'x',{
value:1,
writable:true, //false让它变为只读的意思
configurable:true,
enumerable:false
}}
- 引申
Object.defineProperties()方法
Object.defineProperties({},{
x:{value:1,writable:true,configurable:true,enumerable:false},
y:{get:function(){return 1},enumerable:true,configurable:true}
})
- 引申
Object.getOwnPropertyNames()方法
定义:返回一个对象可枚举、不可枚举属性的名称;
- 引申
Object.keys()方法
定义:返回一个对象可枚举属性的字符串数组;
**基础知识二十:扫盲isPrototypeOf()方法
**
script type="text/javascript">
function Person(){}
Person.prototype.name="lth";
Person.prototype.age=18;
Person.prototype.job="unemployment";
Person.prototype.sayName=function(){
return this.name;
}
var person1=new Person();
var person2=new Person();
document.write(person1.sayName());
//下面我们来分别检测一下person1与person2分别是否处在Person原型对象的原型链中
document.write(Person.prototype.isPrototypeOf(person1)); //true
document.write("<br>");
document.write(Person.prototype.isPrototypeOf(person2)); //true
基础知识二十一:实现一个九九乘法表
var table=new Array(10);
for(var i=0;i<table.length;i++){
table[i]=new Array(10)
}
for(var row=0;row<table.length;row++){
for(var col=0;col<table[row].length;col++){
table[row][col]=row*col
}
}
var p=table[5][7]
基础知识二十二:扫盲map()方法、filter()方法、indexOf()方法
传递给map()的函数应该有返回值,返回的是新数组,如果处理的是稀疏数组返回的也是相同方式的稀疏数组,它们具有相同长度和缺失元素
[1,undefined,2].map((v)=>{return v})
--->(3) [1, undefined, 2]
filter()会跳过稀疏数组中缺少的元素,它返回的数组总是稠密的
[1,undefined,2].filter((v)=>{return true})
--->(3) [1, undefined, 2] //因为数组明给了undefined并且用return true(全部值返回)
[1,undefined,2].filter((v)=>{return v})
--->(2) [1, 2] //自动过滤了undefined
- 利用indexOf()方法在一个数组中搜索指定的值并返回包含所有匹配的数组索引的一个数组
//在数组中查找所有出现的x,并返回一个包含匹配索引的数组
function findall(arr,x){
var result=[];
var len=arr.length;
position=0;
while(position<len){
var index=arr.indexOf(x,position)
if(index<-1){break}
result.push(arr[index])
position++;
}
return results;
}
基础知识二十三:何谓类数组?
- 模拟一个类数组并遍历它
var a={}
var i=0;
while(i<10){
a[i]=i*i; //有索引
i++;
}
a.length=i; //有length属性
var total=0;
for(var j=0;j<a.length;j++){
total+=a[j]
}
- 类数组对象没有继承自
Array.prototype不能直接调用数组方法但可以通过间接调用的方式Array.prototype.join.call({a:1,b:2,length:2},"+")
var a={"0":1,"1":2,length:3}
--->undefined
Array.prototype.join.call(a,"+")
--->"1+2+"
Array.prototype.join.call(a)
--->"1,2,"
var a={"0":1,"1":2,length:2}
--->undefined
Array.prototype.join.call(a)
--->"1,2"
Array.prototype.slice.call(a,0)
--->(2) [1, 2]
基础知识二十四:函数的使用方式
- 作为函数
- 作为方法
- 作为构造函数
- 通过它们的call和apply方法间接调用
var obj={
m:function(){
var self=this;
console.log('obj.m()',this===obj);
f();
function f(){
console.log('f()中this',this===obj);
console.log('事先保存的self',self===obj);
}
}
};obj.m();
var s=function(){return this}()
- 区别
parameter形参、argument实参、arguments实参对象 - 标识符arguments是指向实参对象的引用,实参对象是一个类数组对象,这样可以通过数字下标就能访问传入函数的实参值,它不是真正的数组,,每个实参对象都包含以数字为索引的一组元素以及length属性
- 区别实参对象
arguments的callee和caller属性 - callee表示当前正在执行的函数,这有利于匿名函数的递归或者保证函数的封装性
var factorial=function(x){
if(x<=1){return 1};
retutn x* aargument.callee(x-1)
}
function calleeDemo() {
alert(arguments.callee);
}; calleeDemo()
//用于验证参数
function calleeLengthDemo(arg1, arg2) {
if (arguments.length==arguments.callee.length) {
window.alert("验证形参和实参长度正确!");
return;
} else {
alert("实参长度:" +arguments.length);
alert("形参长度: " +arguments.callee.length);
}
};
calleeLengthDemo(1, 2,3,4)
alert: 实参长度:4
alert: 形参长度: 2
- 函数可以赋值给其他的变量并能正常工作
function sum(x){
return x+1
}
var s=sum;
sum(2)
s(2)
- 函数可以不用带名字直接把他们赋值给数组元素
var a=[function(x){return x+1},20]
a[0](a[1])
- 自定义函数属性
function counter(){
var n=0;
return {
count:function(){ return n++},
reset:function(){n=0}
}
}
var scope='global scope';
function check(){
var scope='local scope';
function f(){return scope};
return f;
};
check()()
--->"local scope"
- 函数与getter、setter结合
function counter(x){
return{
get count(){return x++},
set count(m){
if(m>x){x=m}else{throw Error("sorry,Cannot run less than this value")}}
}
}
var c=counter(1000)
c.count ---> 1000
c.count --->1001
c.count=2000
c.count --->2000
c.count=2000 --->报错
function addPrivateProperty(o, name, predicate) {
var value;
o["get" + name] = function() { return value; };
o["set" + name] = function(v) {
if (predicate && !predicate(v))
throw Error("set" + name + ": invalid value " + v);
else{
value = v;
}
};
}
var o = {};
addPrivateProperty(o, "Name", function(x) { return typeof x == "string"; });
o.setName("Frank"); // Set the property value
console.log(o.getName()); // Get the property value
o.setName(0);
- 函数的length属性是只读属性,它代表着函数实参的
- apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即A对象应用B对象的方法。
- call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法。
- bind方法返回一个新的函数
//粗略的模拟实现bind()方法
function bind(f,o){
if(f.bind){return f.bind(o)}
else{return function(){
return f.apply(o,arguments)
}}
}
- 类的所有实例对象都从同一个原型对象上继承属性
r instanceof Range
实际上instanceof运算符并不会检查r是否是由Range()构造函数初始化而来,而会检查r是否继承自Range.prototype
var F =function(){}
var p=F.prototype
var c=p.constructor
c===F TRUE
//对于任意函数 F.prototype.constructor=F
-** 重写原型的对象时constructor需要加回来**
Range.prototype={
constructor:Range,
xx:value
}
//构造函数的原型对象时constructor属性会丢失
//手动显式的补回来
//使用Range.prototype.xx=function(){}
-
isPrototypeOf检测对象的原型链上是否存在某个特定的原型对象
-
变量和函数声明提升**,赋值不提升**,函数声明提前于变量声明,函数声明会被优先提升
fn() // 1
function fn() {
console.log(1);
};
fn() // 1
- var声明的全局变量等同于给window添加属性,以及函数声明的函数也会成为window属性,dom页面上具有id属性的element节点也会被挂载到window对象中
var a = 1;
// window上可以找到这条属性
window.a; //1
function acfun() {
console.log(1);
};
// window上可以找到这个方法
window.acfun(); //1
Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。并不会获取到原型链上的属性。
- Object.preventExtensions()
将对象设置为不可扩展的,不能给对象添加任何新属性
- Object.seal()阻止给对象添加任何新属性,将当前已有的属性设置为不可配置的,不能删除已有属性
- Object.freeze()同样会把所有属性设置为只读和不可配置的
Object.creat(null)创建一个不包含原型的对象
基础知识二十五:正则表达式
-
将直接量字符单独放进方括号就组成了字符类
-
/[abc]/就和字母'a','b','c'中的任意一个都匹配 -
'^'在方括号内表示符号来定义否定字符类 -
/[^abc]/匹配的是'a','b','c'之外的所有字符 -
字符类可以使用连字符来表示字符范围 -
/a-zA-Z0-9/表示匹配拉丁字母表中任何字母和数字 -{n,m}匹配前一项至少n次但不能超过m次 -
{n,}匹配前一项n次或更多次 -
{n}匹配前一项n次 -
?表示{0,1} -
+表示{1,} -
*表示{0,}-/ab|cd|ef/可以匹配字符串'ab'或者'cd'或者'ef',从左到右,左边的选项匹配,则忽略右边的选项 -
正则表达式的圆括号有两个作用,一个作用是把单独的项组合成子表达式的,以便可以像处理一个独立的单元格那样用,一个是在完整的模式中定义子模式
/(ab|cd)+|ef/
基础知识二十六:扫盲迭代器iteraor和生成器generator
-
迭代器它可以遍历任何可以迭代的对象 迭代器是一个对象,这个对象允许对它的值集合进行遍历,并保持任何必要的状态以便能跟踪到当前遍历的位置
-
迭代器必须包含next()方法,每一次调用next()都返回集合的下一个值
//模拟实现一个迭代器函数
function i(counter){
let nextValue=Math.round(counter)
return {
next:function(){return nextValue++}
}
}
let serial=counter(1000)
let sn1=serial.next() //1000
let sn2=serial.next() //1001
unction range(f,l){
let nextvalue=Math.ceil(f);
return {
next:function(){
if(nextvalue>l){throw StopIteration};
return ++nextvalue;
}
}
}
let r=range(1,5);
while(true){
try{
console.log(r.next())
}catch(e){
if(e==StopIteration){break}else{throw e}
}
}
依次输出
2
3
4
5
6
-
for/in循环会自动调用它的__iterator__()方法来获得一个迭代器对象,然后调用它的迭代器的next()方法,将返回值赋给循环变量。会自己处理StopIteration异常
-
可迭代的对象和迭代器对象是两个概念
-
可迭代的对象中有迭代器对象,迭代器对象中有next()方法
基础知识二十七:javascript:url用法
- 能识别的资源是转换为字符串的执行代码的返回值,如果代码返回undefined那么这个资源是没有内容的
部分浏览器会执行url里面的代码并使用返回的字符串作为待显示新文档的内容
基础知识二十八:defer和async属性
- 在支持两者的浏览器中async优于defer执行,并忽略defer
- defer属性使得浏览器延迟脚本执行,直到文档的载入和解析完成,并可以操作
- async属性使得浏览器可以尽快的执行脚本,而不用在下载脚本时阻塞文档解析
- 它们都可以让文档先解析完成
<script defer src='a.js'></script>
<script async src='b.js'></script>
拓展学习:
data.x应该保留着data-x属性的值
data.jqueryTest保留着data-jquery-test
getAttribute('width') 属性值都被看作是字符串
判定一个元素的尺寸和位置最简单的方法是
getBoundingClientRect()
left
right
bottom
top
所返回的坐标包含元素的边框和内边距
但不包含元素的外边距
所有的定位元素都需要加上单位
需要将值放在引号内
e.style.left='300px'
- js元素定义了classList属性,该属性值是DOMTokenList对象
add()和remove()从元素的class元素的class属性中添加和清除一个类名
toggle()表示如果如果不存在类名就添加一个,否则删除它
contains()方法检测class属性中是否包含一个指定的类名
className
- 事件类型(event type)是一个用来说明发生什么类型事件的字符串,例如‘mousemove’表示用户移动鼠标 ‘keydown’表示用户键盘某个键被按下
- 事件目标(event target)是发生的事件或与之相关的对象
- 事件处理函数(event handler)或事件监听程序(event listener)是处理或响应事件的函数
- 当在特定的目标上发生特定类型的事件时,浏览器会调用对应的处理程序,当对象上注册的事件处理程序被调用时,我们有时会说浏览器‘触发’(fire,trigger)和派发(dispatch)了事件
- 使用addEventListener()事件注册时,调用的处理程序使用事件目标作为他们的this,对于attachEvent()来讲,在其注册的处理程序作为函数调用,this指向window对象
- 使用addEventListener()注册的处理程序按照它们的注册顺序调用使用attachEvent()注册的处理程序可能按照任何顺序调用,所以代码不应该依赖于调用程序