[JS] JS Basic : compare with c#
Ref: React从入门到精通视频教程
Ref: C# 教程
Ref: [Unity3D] C# Basic : Gameplay Scripting
/* 之前的js总结有点low, 这次通过对比c#相关特性掌握js */
打印
js
console.log("...")
alart("...")
c#
Console.WriteLine("...");
print("...") // for unity.
注释 ...
变量 - 类型判断
js
typeof(<var>)
c#
<object>.GetType()
变量 - 类型转换
js > var a = 120 undefined > var b = "kg" undefined > a + b '120kg' c# String str = "hao"; int a = 123; Console.WriteLine(str + a); // 自动 .toString() > hao123
变量 - String
数组
js
var a = Array(4)
<arr>.push(...)
<arr>.pop()
<arr>.shift() // 删除第一个元素
delete <arr>[3] // 不是删除而是清空变为undefined.
<arr>.splice(3) // 这个才是删除
<arr>.concat(<arr2>)
c#
double[] balance = new double[10];
int[] marks = new int[5] { 99, 98, 92, 97, 95}; // 这里的5必须与后面的个数一致
int[] score = marks; // 指向相同的内存位置
条件判断 - 等价
js
Ref: Javascript 中 == 和 === 区别是什么?
一些违反直觉的结果:【'0'不是遵循ascii,是从0开始计算】
值判断:数值、字符串、布尔值
地址判断:对象、数组、函数
> 0 == '' true
> 0 == '0' true
> false == '0' true
> null == undefined true
> '\t\r\n' == 0 true
> undefined == true
false
> undefined == false
false
> !undefined
true
> !null
true
> a = 'hao'
'hao'
> b = 'hao'
'hao'
> a == b
true
> a === b
true
c#
Ref: C#中的==、Equal、ReferenceEqual
ReferenceEquals: 是否是同一个对象的不同引用【指向同一个对象,当然相等,比较严格】
自定义类型需要重载==,否则没法使用(编译)
字符串比较:==效果等价equal
using System.IO; using System; class Program { static void Main() { Console.WriteLine("Hello, World!"); String str1 = "hao"; String str2 = "hao"; Console.WriteLine( str1.GetHashCode() ); // output: 696029526 Console.WriteLine( str2.GetHashCode() ); // output: 同上 if (Object.ReferenceEquals(str1, str2)) { Console.WriteLine("ReferenceEquals returns true"); // 会执行 } } }
条件判断 - switch ...
循环 - while ...
循环 - for
js
- for (... in ... ) 方法 - 遍历对象的key
- for (... of ... ) 方法 - 遍历对象的value
Ref: javascript总for of和for in的区别?
> let aArray = ['a',123,{a:'1',b:'2'}] undefined
> aArray [ 'a', 123, { a: '1', b: '2' } ]
----------------------------------------- > for(let index in aArray){ ... console.log(`${aArray[index]}`); ... }
a 123 [object Object] undefined
----------------------------------------- > for(var value of aArray){ ... console.log(value); ... }
a 123 { a: '1', b: '2' } // <---- 这才是想要的
进一步的,给数组添加一个属性,
> aArray.name = 'demo' 'demo'
> aArray [ 'a', 123, { a: '1', b: '2' }, name: 'demo' ]
Jeff: 属性就是属性,不能算是数组的表现部分。
但是,for ... in会打印出新添加的属性;for ... of 则不会。
如果实在想用for...of
来遍历普通对象的属性的话,可以通过和Object.keys()
搭配使用,
先获取对象的所有key的数组,然后遍历:
var student={ name:'wujunchuan', age:22, locate:{ country:'china', city: 'xiamen', school: 'XMUT' } }
for(var key of Object.keys(student)){ //使用Object.keys()方法获取对象key的数组 console.log(key+": "+student[key]); }
c#
Ref: C#中数组Array、ArrayList、泛型List<T>的比较
- ArrayList类型
ArrayList myAL = new ArrayList(); myAL.Add("Hello"); myAL.Add("World"); myAL.Add("!");
foreach ( Object obj in myAL ) Console.Write( "{0}", obj );
小问题:
ArrayList解决了数组中所有的缺点,但是在存储或检索值类型时通常发生装箱和取消装箱操作,带来很大的性能耗损。尤其是装箱操作
这样在ArrayList中插入不同类型的数据是允许的。因为ArrayList会把所有插入其中的数据当作为object类型来处理;
在使用ArrayList处理数据时,很可能会报类型不匹配的错误,也就是ArrayList不是类型安全的。
解决方法就是泛型。
- List<T>类型
List<T> 是类型安全的,在声明List集合时,必须为其声明List集合内数据的对象类型。
List<string> dinosaurs = new List<string>(); dinosaurs.Add("Tyrannosaurus"); dinosaurs.Add("Amargasaurus"); foreach (string dinosaur in dinosaurs) { Console.WriteLine(dinosaur); }
难点,喝杯茶,研究下
函数 - 匿名函数
js
1. 基本形式为:【使用圆括号】
( function(){ <这里是块级作用域> } )(); // 有了后面的(),匿名函数 --> 立即执行函数
( function () { /* code */ } () );
注意:匿名函数的作用是避免全局变量的污染以及函数名的冲突。
2. 如果不想用圆括号:
因为Javascript将function关键字当作一个函数声明的开始,而函数声明后面不能加圆括号,如果你不显示告诉编译器,它会默认生成一个缺少名字的function,并且抛出一个语法错误。
Sol: 在function前面加个小东西就好了,如更多的写法,详见:JS匿名函数理解
使用-/+运算符 -function(x,y){ alert(x+y); return x+y; }(3,4); 或者+, --, ++
使用波浪符(~)
等等……
还有一些比较奇怪的写法,Ref: 杂七杂八JS :深入理解 函数、匿名函数、自执行函数
~function(x){ alert(x); }(5);//5
> -1
---------------------
!function(x){ alert(x); }(4);//4
> true
---------------------
0, function(x){ alert(x); }(3);//3
> undefined
---------------------
true && function(x){ alert(x); }(2);//2
> undefined
---------------------
false && function(x){ alert(x); }(2);//2
> false
3. 为什么需要匿名函数
<script type="text/javascript"> var s = document.getElementsByTagName('input'); //获取整个网页的标签
window.onload = function() { for(var i = 0;i < s.length;i++){ s[i].onclick = show(i); // js 没有块级作用域,你用循环赋值给每一个 s[i] 的方法的参数都是一个i的引用 } }; function show(num){ alert("你好,请"+num+"号嘉宾领奖") } </script>
方案:利用 IIFE(立即执行函数)模拟了一个块级作用域解决了这个问题。
你声明了一个匿名函数,同时立即执行它自己。num 是函数接受的参数,i是执行函数时传入的值。
4. 箭头函数(Lambda表达式)
匿名函数有两种语法风格:Lambda表达式(lambda-expression)和匿名方法表达式(anonymous-method-expression)。在几乎所有的情况下,Lambda表达式都比匿名方法表达式更为简介具有表现力。
箭头函数要实现类似纯函数的效果,必须剔除外部状态。所以当你定义一个箭头函数,在普通函数里常见的this
、arguments
、caller
是统统没有的。
什么是this
首先,什么是this, 参见:Javascript的this用法(阮一峰)
this就是调用函数的那个对象。
- 情况一:纯粹的函数调用
var x = 1; function test(){ this.x = 0; // this就代表全局对象Global }
test(); alert(x); //0
注意:与下面两个相比,没有对象o;有了对象后,this的范围由global变为了对象范围内。
- 情况二:作为对象方法的调用
function test(){ alert(this.x); // this就指这个上级对象 } var o = {}; o.x = 1; o.m = test; o.m(); // 1
- 情况三:作为构造函数调用
function test(){ this.x = 1; } var o = new test(); alert(o.x); // 1
- 情况四:apply调用
apply()是函数对象的一个方法,它的作用是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。因此,this指的就是这第一个参数。
var x = 0; function test(){ alert(this.x); } var o={}; o.x = 1; o.m = test; o.m.apply(); //0, apply()的参数为空时,默认调用全局对象
或者:
o.m.apply(o); //1, 这时this代表的是对象o
匿名函数没有this
在匿名函数中,没有this, arguments, caller。
function foo() { this.a = 1 let b = () => console.log(this.a) // 不是匿名函数的this,而是外层函数foo的this b() } foo() // 1
同理:
function foo() { return () => console.log(arguments[0]) // 同理,这也是外层函数foo的arguments,1,而非3。 } foo(1, 2)(3, 4) // 1
易犯错
- 一个经常犯的错误是使用箭头函数定义对象的方法,如:
let a = {
foo: 1,
bar: () => console.log(this.foo) // 对象a
并不能构成一个作用域,所以再往上 反而到达了全局作用域,所以不是a.foo
}
a.bar() //undefined
let
允许你声明一个作用域被限制在块级中的变量、语句或者表达式。
与var关键字不同的是,它声明的变量只能是全局或者整个函数块的。
- 另一个错误是在原型上使用箭头函数,如:
function A() { this.foo = 1 } A.prototype.bar = () => console.log(this.foo) // this不是指向A,,而是根据变量查找规则回溯到了全局作用域 let a = new A() a.bar() //undefined
通过以上说明,我们可以看出,箭头函数除了传入的参数之外,真的是什么都没有!
如果你在箭头函数引用了this
、arguments
或者参数之外的变量,那它们一定不是箭头函数本身包含的,而是从父级作用域继承的。
在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不是基于‘类的',而是通过构造函数(constructor)和原型链(prototype chains)实现的。
为了解决构造函数的对象实例之间无法共享属性的缺点,js提供了prototype属性。
5. 什么情况下该使用箭头函数
- 箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如用在
map
、reduce
、filter
的回调函数定义中; - 不要在最外层定义箭头函数,因为在函数内部操作
this
会很容易污染全局作用域。最起码在箭头函数外部包一层普通函数,将this
控制在可见的范围内; - 如开头所述,箭头函数最吸引人的地方是简洁。在有多层函数嵌套的情况下,箭头函数的简洁性并没有很大的提升,反而影响了函数的作用范围的识别度,这种情况不建议使用箭头函数。
比如,在对象中定义的简单的方法,匿名函数看上去会简洁一些。
毕竟,有了beyoud.showArtist,没必要再给函数在取一个名字。
c#
一些匿名函数的示例
x => x + 1 //隐式的类型化,函数体为表达式 x => {return x + 1;} //隐式的类型化,函数体为代码块 (int x) => x + 1 //显式的类型化,函数体为表达式 (int x) => {return x + 1;} //显式的类型化,函数体为代码块 (x , y) => x * y //多参数 () => Console.WriteLine() //无参数 async (t1 , t2) => await t1 + await t2 //异步 delegate (int x) {return x + 1;} //匿名函数方法表达式 delegate {return 1 + 1;} //参数列表省略
Lambda表达式和匿名方法表达式的区别:
● 当没有参数的时候,匿名方法表达式允许完全省略参数列表,从而可以转换为具有任意值参数列表的委托类型,Lambda表达式则不能省略参数列表的圆括号()。
● Lambda表达式允许省略和推断类型参数,而匿名方法表达式要求显式声明参数类型。
● Lambda表达式主体可以为表达式或者代码块,而匿名方法表达式的主体必须为代码块。
● 只有Lambda表达式可以兼容到表达式树类型。
Ref: C# Lambda expressions: Why should I use them?
简单的嵌入式的函数,采用匿名方式会阅读更友好一些。
// anonymous delegate var evens = Enumerable .Range(1, 100) .Where(delegate(int x) { return (x % 2) == 0; }) .ToList(); // lambda expression var evens = Enumerable .Range(1, 100) .Where(x => (x % 2) == 0) // <---- 这里看上去格式简洁了许多! .ToList();
课外阅读
委托,定义非常类似于函数,但不带函数体。类似于 C 或 C++ 中函数的指针。
using System; delegate int NumberChanger(int n); // 声明一个委托delegate:引用带有一个整型参数的方法,并返回一个整型值
namespace DelegateAppl { class TestDelegate { static int num = 10; public static int AddNum(int p) { num += p; return num; } public static int MultNum(int q) { num *= q; return num; } public static int getNum() { return num; } static void Main(string[] args) { // 创建委托实例 NumberChanger nc1 = new NumberChanger(AddNum); NumberChanger nc2 = new NumberChanger(MultNum);
// 使用委托对象调用方法 nc1(25); Console.WriteLine("Value of Num: {0}", getNum()); nc2(5); Console.WriteLine("Value of Num: {0}", getNum()); Console.ReadKey(); } } }
通过事件使用委托,在类的内部声明事件,首先必须声明该事件的委托类型;然后,声明事件本身,使用 event 关键字。
public delegate void BoilerLogHandler(string status); // 好比一个“函数指针类型” // 基于上面的委托定义事件 public event BoilerLogHandler BoilerEventLog;
之后,该事件在生成的时候会调用委托;有点trigger函数的意思。
事件使用 发布-订阅(publisher-subscriber) 模型。
-
- 包含事件的类用于发布事件 ---- 发布器(publisher) 类
- 接受该事件的类 ---- 订阅器(subscriber) 类
using System; namespace SimpleEvent { using System;
/***********发布器类***********/ public class EventTest { private int value; public delegate void NumManipulationHandler(); public event NumManipulationHandler ChangeNum; protected virtual void OnNumChanged() { if ( ChangeNum != null ) { ChangeNum(); /* 事件被触发 */ }else { Console.WriteLine( "event not fire" ); Console.ReadKey(); /* 回车继续 */ } } public EventTest() { int n = 5; SetValue( n ); } public void SetValue( int n ) { if ( value != n ) { value = n; OnNumChanged(); } } } /***********订阅器类***********/ public class subscribEvent { public void printf() { Console.WriteLine( "event fire" ); Console.ReadKey(); /* 回车继续 */ } }
/**************************/ /***********触发***********/
/*************************/
public class MainClass { public static void Main() { EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */ subscribEvent v = new subscribEvent(); /* 实例化对象 */
e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册,看看有多少人要订阅这个事件触发 */ e.SetValue( 7 ); e.SetValue( 11 ); } } }
函数 - 闭包
js
在函数外部,自然无法读取函数内的局部变量。
函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
如何从外部读取局部变量?
function f1(){
var n=999;
function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999
让这些变量的值始终保持在内存中。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
【可能的问题需注意:内存消耗很大】
function f1(){ var n=999; nAdd=function() { n+=1 } function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999, 这里我们使用了f1内部的n。 nAdd(); // 说明起了作用,让n改变了,且一直保存在内存中 result(); // 1000
思考:如何调用object中的name,如下所示。通过var that,这样this就不是global了。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function() {
// var that = this;
return function(){ return this.name;
// return that.name; }; } }; alert(object.getNameFunc()());
c#
C#中通常通过匿名函数和lamada表达式来实现闭包。
Ref: C# 闭包问题-你被”坑“过吗?
public static void Main() { Console.WriteLine("Starting."); for (int i = 0; i < 4; ++i) { int j = i; Task.Run( () => Console.WriteLine(j) ); // 如果采用i, 将传入的是引用 } Console.WriteLine("Finished. Press <ENTER> to exit."); Console.ReadLine(); }
从本质上说,闭包是一段可以在晚些时候执行的代码块,但是这段代码块依然维护着它第一个被创建时环境(执行上下文)。
using System; class Test { static void Main() { Action action = CreateAction(); action(); action(); }
--------------------------------------------------
static Action CreateAction() { int counter = 0; return delegate // 好比函数指针! { // Yes, it could be done in one statement; // but it is clearer like this. counter++; Console.WriteLine("counter={0}", counter); }; } }
C#中通常通过"匿名函数"和"lamada表达式"来实现闭包。
var values = new List<int> { 100, 110, 120 }; var funcs = new List<Func<int>>(); foreach (var v in values) funcs.Add( () => { //Console.WriteLine(v); return v; } ); foreach (var f in funcs) Console.WriteLine(f()); Console.WriteLine("{0}{0}", Environment.NewLine); funcs.Clear(); for (var i = 0; i < values.Count; i++) { //var v2 = values[i]; funcs.Add(() => { var v2 = values[i]; //will throw exception return v2; }); } foreach (var f in funcs) Console.WriteLine(f());
为什么要用闭包closure,另开一个专题学习。
Goto: [JS] This is ”Closure“