[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
  

c#
Goto: 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. 为什么需要匿名函数

Ref: javascript什么是匿名函数和闭包?

<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表达式)

Ref: JS中lambda表达式的优缺点和使用场景

匿名函数有两种语法风格:Lambda表达式(lambda-expression)匿名方法表达式(anonymous-method-expression)。在几乎所有的情况下,Lambda表达式都比匿名方法表达式更为简介具有表现力。

箭头函数要实现类似纯函数的效果,必须剔除外部状态。所以当你定义一个箭头函数,在普通函数里常见的thisargumentscaller是统统没有的

什么是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

 

通过以上说明,我们可以看出,箭头函数除了传入的参数之外,真的是什么都没有!

如果你在箭头函数引用了thisarguments或者参数之外的变量,那它们一定不是箭头函数本身包含的,而是从父级作用域继承的。

在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不是基于‘类的',而是通过构造函数(constructor)和原型链(prototype chains)实现的。

为了解决构造函数的对象实例之间无法共享属性的缺点,js提供了prototype属性。

 

5. 什么情况下该使用箭头函数

  1. 箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如用在mapreducefilter的回调函数定义中;
  2. 不要在最外层定义箭头函数,因为在函数内部操作this会很容易污染全局作用域最起码在箭头函数外部包一层普通函数,将this控制在可见的范围内
  3. 如开头所述,箭头函数最吸引人的地方是简洁。在有多层函数嵌套的情况下,箭头函数的简洁性并没有很大的提升,反而影响了函数的作用范围的识别度,这种情况不建议使用箭头函数。

比如,在对象中定义的简单的方法,匿名函数看上去会简洁一些。

毕竟,有了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“

 

posted @ 2018-01-26 17:00  郝壹贰叁  阅读(363)  评论(0编辑  收藏  举报