CuriousY A world with wonder

Reading <Types & Grammar> - 2

| Comment

Chapter 4: Coercion

Converting Values

Converting a value from one type to another is often called “type casting,” when done explicitly, and “coercion” when done implicitly (forced by the rules of how a value is used).

显式类型转换和隐式类型转换对应的英文。


The terms “explicit” and “implicit,” or “obvious” and “hidden side effect,” are relative.

If you know exactly what a + "" is doing and you’re intentionally doing that to coerce to a string, you might feel the operation is sufficiently “explicit.” Conversely, if you’ve never seen the String(..) function used for string coercion, its behavior might seem hidden enough as to feel “implicit” to you.

这段挺有意思,即隐式或显式是相对的,即一个人如果对某种方式进行的类型转换非常熟悉,那么这种方式对于他来说就是显示的,反之亦然。

当然这有一些诡辩的感觉,毕竟你写的代码不是只有你一个人看的,那么可以认为大多数人熟悉的类型转换方式为显式,否则为隐式比较合适。


Abstract Value Operations

The JSON.stringify(..) utility will automatically omit undefinedfunction, and symbol values when it comes across them. If such a value is found in an array, that value is replaced by null (so that the array position information isn’t altered). If found as a property of an object, that property will simply be excluded.

Consider:

JSON.stringify( undefined );                    // undefined
JSON.stringify( function(){} );                 // undefined

JSON.stringify( [1,undefined,function(){},4] ); // "[1,null,null,4]"
JSON.stringify( { a:2, b:function(){} } );      // "{"a":2}"

JSON.stringify(..)使用注意事项1。此外,如果函数对象拥有toJSON方法,则序列化时会使用这个方法的返回值,而不是undefined或是被忽略了。(另一个角度,即toStringtoJSON这类方法可以看作是这些JavaScript标准库提供的对应的hook,方便其他代码更好地适用于标准库方法。)

Read more

关于JavaScript中的跨域请求

| Comment

在接触JavaScript之前我是不知道跨域请求的概念的,原因是后端的http请求并没有类似的限制。所谓跨域请求是指一个http请求,它的请求方与被请求方的域名不同(注意这里的域名是包括端口的,即http://localhost:5000和http://localhost:4000是不同的)。那么为什么在JavaScript中需要对跨域的请求添加限制呢?以及怎样才能在JavaScript中实现跨域的请求呢?

跨域请求的限制

默认情况下,在JavaScript中是做跨域的请求是会以失败而告终的,这并不是JavaScript语言本身的限制(比如用Node.js完全可以做一个跨域请求),而是浏览器方面施加的限制。浏览器从安全方面考虑(这个安全更多的是指服务器端的安全,比如如果没有跨域请求的限制的话,攻击者可以将获取某个资源的脚本恶意插入到各大网站上,所有登陆网站的人的电脑都会自动执行该脚本,从而可以实现DDOS攻击),禁掉了跨域请求的功能,即所有在浏览器中运行的JavaScript代码中的http请求都会经过浏览器的检查,如果请求的目的地的域名与此时执行JavaScript代码的域名不一致则会抛出一个异常。

在JavaScript中实现跨域请求

虽然跨域请求是有风险的,但不排除有一些网络服务做了很好的准备,能承担跨域请求的风险,这些网络服务就可以告诉浏览器,访问它们就不需要再检查请求是否跨域了。为此,W3C设定了一组标准,可以让服务器端支持跨域请求,称之为Cross-Origin Resource Sharing,简称CORS。在实际操作中,网络服务通过一组协定好的http header来告诉浏览器它们支持跨域请求:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Credentials
  • Access-Control-Expose-Headers
  • Access-Control-Max-Age
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

以上,只要服务器端响应带有带有以上http header的第一个(即Access-Control-Allow-Origin是必须的,其他都是可选项),就表示它支持跨域请求,该http header的含义是服务端允许发起请求的域名,可以指定一个域名,只有该域名下的跨域请求能进行跨域访问,比如:

Access-Control-Allow-Origin: http://api.bob.com

当然,更多情况下,你可能是希望任何域名都能请求的:

Access-Control-Allow-Origin: *

其他相关的http header都用来做一些额外的其他限制,比如说Access-Control-Allow-Methods可以限定跨域请求的方法只能是POST等。

而客户端的请求也可以对它自己的跨域请求作一些限制,也是通过在请求中添加http header的方式,以下http header和跨域请求的请求有关:

  • Origin
  • Access-Control-Request-Method
  • Access-Control-Request-Headers
Read more

React进阶之使用Redux管理组件状态

| Comment

刚刚接触Redux,看了官方的文档例子以及redux-tutorial项目之后,写了一个简单的半成品项目来练手,其中有4个branch,分别对应了使用全局变量来管理组件状态、使用React原生的flux库、使用redux和使用reduxreact-redux对同一个问题的四种解决方法,有兴趣可以去看一看。下面讲一讲写了这个半成品项目之后的一些感受和小结。

明确组件的属性

默认情况下,React组件的属性是可以任意赋值的,为了对此加以限制,在定义React组件时,可以同时定义好它允许设置的属性以及对应的类型(通过定义组件propTypes属性来限定),比如:

import React, {Component, PropTypes} from 'react';

class MyButton extends Component {
    static propTypes = {
        onClick: PropTypes.func.isRequired,
        completed: PropTypes.bool.isRequired,
        text: PropTypes.string.isRequired
    };

    render() {
        return (
            <button className="Button" onClick={this.props.onClick}>{this.props.text}</button>
        );
    }
}

这样做的好处是,设计组件时就可以定义好它的属性以及各属性的作用,使定义更明确,并且类似于接口的定义,之后使用组件时(redux就会大量使用组件的属性)也因此不会胡乱使用了,减少了bug出现的概率。

Without redux

不使用redux,一种比较直接的方案是通过全局变量来分发状态的变化。

以下,在Screen组件中将组件本身赋予了一个全局变量:

class Screen extends Component {
    constructor(props) {
        super(props);
        this.state = {
            value: '0',
        };
        window.SCREEN_OBJ = this;
    }

    render() {
        return (
            <div className="Screen">{this.state.value}</div>
        );
    }
}

Button组件中,因此可以调用Screen组件的setState函数:

class Button extends Component {
    handleClick() {
        let newValue = window.SCREEN_OBJ.state.value + this.props.name;
        window.SCREEN_OBJ.setState({value: newValue});
    }

    render() {
        return (
            <button className="Button" onClick={this.handleClick.bind(this)}>{this.props.name}</button>
        );
    }
}
Read more

React实践初体验

| Comment

React快速入门可以看阮一峰的一篇日志和对应的demo

这里提一下刚入门(断断续续研究了两周时间)的我关注到的一些点:

关于JSX

React发明的JSX的语法可以看作是原生JavaScript的基础上创建了一系列语法糖(原生JavaScript可不允许混入HTML代码),在实际运行时它会先被编译成JavaScript代码,再被进一步编译运行。既然是语法糖,当然是可以越过它直接使用原生JavaScript来写React程序的(参考这里),不过还是建议直接用这些语法糖,方便并且大家都这么用(符合规范)。

关于JSX的debug

由于JSX是编译到JavaScript之前的代码,debug会比较麻烦,目前的一个解决方案是使用React官方的一个开发用的浏览器插件以及console.log

关于JSX的编译

把JSX编译到JavaScript的过程既可以事先做好,再把编译过的js代码作为资源来加载,也可以事先不做,让页面直接加载JSX资源,并用浏览器来编译JSX代码。要让浏览器做这件事需要额外加载browser.js(使浏览器支持babel),并且加载的JSX资源类型需要是"text/babel"(作为比较,原生JS的类型为"text/javascript")。当然从效率上来说,事先编译好肯定更优。

关于React和Node.js

对于应用于前端和应用于后端(这里所说的应用到后端是指后端是Node.js),React库是一模一样的,不信可以对比两者用到的React的源码。你看到的用法上有时候会有一些差异,其实并不是由于应用在前端或后端而导致的差异,而是是否使用了ES6的语法的差异(见下面一条)。

关于React和ES6

使用pre-ES6和ES6的语法写React是有一些区别的,你可以理解为pre-ES6的代码是ES6代码的“polyfill”的结果。 关于它们的区别以及如何从pre-ES6迁移到ES6版的React代码,可以参考这篇文章。 这里简单总结下,ES6相对pre-ES6而言:

  • 不再使用getDefaultProps来初始化组件的属性,而是通过ES6的class定义属性的方式来定义默认的组件属性;
  • 使用class NewComponent extends React.Component来创建组件,而不再用React.createClass
  • 不再自动帮你绑定组件中定义的函数,而需要你在constructor函数中显示地去绑定;
  • 不再使用getInitialState来初始化组件的状态,而是在constructor函数中对state属性进行赋值操作来取代。

最后,来个例子,比如下面的两种方式创建组件(编译出来)是一样的:

let MyComponent = React.createClass({
    getDefaultProps: function () {
        return {unit: 's'};
    },

    getInitialState: function () {
        return {
            result: 0
        };
    },

    _handleClick: function (event) {
        this.setState({
            result: this.state.result + 1
        });
    },

    render: function () {
        return (
            <div>
                <button onClick={this._handleClick}>+1{this.props.unit}</button>
                <span>{this.state.result}</span>
                <span>{this.props.unit}</span>
            </div>
        );
    }
});
class MyComponent extends React.Component {
    static defaultProps = {unit: "s"};

    constructor() {
        super();
        this.state = {
            result: 0
        };
        this._handleClick = this._handleClick.bind(this);
    }

    _handleClick() {
        this.setState({
            result: this.state.result + 1
        });
    }

    render() {
        return (
            <div>
                <button onClick={this._handleClick}>+1{this.props.unit}</button>
                <span>{this.state.result}</span>
                <span>{this.props.unit}</span>
            </div>
        );
    }
}
Read more

An error occurs in for-loops in a shell script

| Comment

问题

想要写一个shell script实现如下功能:

给定一个ip的list和对应的hostname,对每个ip的机器设置静态hostname并重启。并可以远程调用hostname命令来显示所有机器的hostname是否已设置成功。

脚本最初的版本是这样的:

#!/bin/sh
# Get the absolute directory path of this file
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
while read line
do
	array=()
	for word in $line
	do
		array+=($word)
	done

	echo "Setting the hostname of ${array[0]} to ${array[1]}"
	sshpass -p sp1unk ssh -o StrictHostKeyChecking=no root@${array[0]} "hostnamectl set-hostname ${array[1]} --static && reboot"
	echo "====================================================================="
	done <$DIR/host_mapping.txt

所有的ip和hostname信息存在同目录下的host_mapping.txt文件中,每一行为一个条目,ip和hostname以空格间隔。

结果运行下来,设置完第一个ip的hostname程序就结束了。

分析原因

  • 猜想1:因为reboot命令导致ssh链接终端从而使进程直接结束。那么我们去掉reboot试一下,发现仍然是只执行了一个条目就退出了。

  • 猜想2:sshpass这条命令抛出了异常导致程序结束。但实验下来,shell脚本出现异常好像并不影响后面的语句执行,比如下面的脚本:

    #!/bin/sh
    UnkonwnCommand
    echo "Executed here"
    

    输出为:

    ./test.sh: line 2: UnkonwnCommand: command not found
    Executed here
    
  • 猜想3:跟sshpass或ssh这条命令相关,参考stackoverflow。也就是说ssh命令默认会读取后续的所有内容作为它的输入,解决方法是把它的输入连到别的地方去。

解决问题

可远程配置hostname的最终脚本如下:

#!/bin/sh
# Get the absolute directory path of this file
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
while read line
do
	array=()
	for word in $line
	do
		array+=($word)
	done

	echo "Setting the hostname of ${array[0]} to ${array[1]}"
	sshpass -p sp1unk ssh -o StrictHostKeyChecking=no root@${array[0]} "hostnamectl set-hostname ${array[1]} --static && reboot" < /dev/null
	echo "====================================================================="
	done <$DIR/host_mapping.txt
| Page 15 of 25 |