JavaScript原型和原型链

原型继承方法

  • 原型继承有如下2种方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    var BaseCalculator = function() {
        this.decimalDigits = 2;
    };

    BaseCalculator.prototype = {
        add: function(x, y) {
            return x + y;
        },
        subtract: function(x, y) {
            return x - y;
        }
    };
    var Calculator = function () {
        //为每个实例都声明一个税收数字
        this.tax = 5;
    };
    // 原型继承有如下2种方式:
    Calculator.prototype = new BaseCalculator();
    Calculator.prototype = BaseCalculator.prototype;

    var calc =newCalculator();
    alert(calc.add(1, 1));
    alert(calc.decimalDigits);
  • 2种继承方式的区别:

    • 第一种继承要是能够访问到calc.decimalDigits的值
    • 而第二种无法访问到calc.decimalDigits;
    • 因为第一种是将 BaseCalculator的实例赋值给Calculator.prototype, 所以能够访问到其对应的属性, 并且所有Calculator都公用一个BaseCalculator实例, 而第二种没有实例, 所以无法访问

如何判断对象是否有对应的属性比较安全

  • 对象的hasOwnProperty方法, 可以用来判别是否是自己上的属性或方法,
  • 但是对象的hasOwnProperty很容易被修改, 所以我们应该使用
    {}.hasOwnProperty.call(foo, ‘bar’);来判断 更加可靠安全

proto属性

  • 每个对象都有一个__proto__隐式属性, 他指向该对象的原型, 既prototype
  • 可以直接用__proto__属性改变实例对象的原型链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var a = {
  x: 10,
  calculate: function (z) {
    return this.x + this.y + z
  }
};

var b = {
  y: 20,
  __proto__: a
};
var c = {
  y: 30,
  __proto__: a
};

// 调用继承过来的方法
b.calculate(30); // 60
c.calculate(40); // 80

var d = {
x: 20,
calculate: function (z) {
return this.x + this.y + z
}
}
c.__proto__ = d;
c.calculate(40); //90

with会改变作用域

  • with会把原型链__proto__改变到Object.prototype
  • 每个上下文执行环境都有变量对象(variable object),this指针(this value),作用域链(scope chain) 这3个属性
  • 在代码执行过程中,如果使用with或者catch语句就会改变作用域链。而这些对象都是一些简单对象,他们也会有原型链。这样的话,作用域链会从两个维度来搜寻。
    • 首先在原本的作用域链
    • 每一个链接点的作用域的链(如果这个链接点是有prototype的话)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Object.prototype.x = 10;

var w = 20;
var y = 30;

// 在SpiderMonkey全局对象里
// 例如,全局上下文的变量对象是从"Object.prototype"继承到的
// 所以我们可以得到“没有声明的全局变量”
// 因为可以从原型链中获取

console.log(x); // 10

(function foo() {

  // "foo" 是局部变量
  var w = 40;
  var x = 100;

  // "x" 可以从"Object.prototype"得到,注意值是10哦
  // 因为{z: 50}是从它那里继承的

  with ({z: 50}) {
    console.log(w, x, y , z); // 40, 10, 30, 50
  }

  // 在"with"对象从作用域链删除之后
  // x又可以从foo的上下文中得到了,注意这次值又回到了100哦
  // "w" 也是局部变量
  console.log(x, w); // 100, 40

  // 在浏览器里
  // 我们可以通过如下语句来得到全局的w值
  console.log(window.w); // 20

})();

AO变量对象

  • 当进入执行上下文(代码执行之前)时,VO里已经包含了下列属性
    • 函数的所有形参(如果我们是在函数执行上下文中)
    • 由名称和对应值组成的一个变量对象的属性被创建;没有传递对应参数的话,那么由名称和undefined值组成的一种变量对象的属性也将被创建。
    • 所有函数声明(FunctionDeclaration, FD)—由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建;如果变量对象已经存在相同名称的属性,则完全替换这个属性。
    • 所有变量声明(var, VariableDeclaration)—由名称和对应值(undefined)组成一个变量对象的属性被创建;如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
    • 函数表达式是不在AO对象里生成变量, 但是把函数表达式赋值给一个声明的变量, 那边这个变量会以undefined的方式存放在AO对象里

全局变量和var变量的区别

  • 关于变量,还有一个重要的知识点。变量相对于简单属性来说,变量有一个特性(attribute):{DontDelete},这个特性的含义就是不能用delete操作符直接删除变量属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    a = 10;
    alert(window.a); // 10

    alert(delete a); // true

    alert(window.a); // undefined

    var b = 20;
    alert(window.b); // 20

    alert(delete b); // false

    alert(window.b); // still 20

    但是这个规则在有个上下文里不起走样,那就是eval上下文,变量没有{DontDelete}特性。
    eval('var a = 10;');
    alert(window.a); // 10

    alert(delete a); // true

    alert(window.a); // undefined

jquery源码心得

阅读jquery源码心得

  • 闭包参数传递window, 是为了寻找window时, 不用寻找到外层, 提高效率, 也为压缩代码提供了方便
  • 闭包声明undefined, 但不赋值, 就是让underfined == ‘underfined’ 防止ie9一下的浏览器 修改其值
  • jquery = function(selector,…) {return new jquery.property.init()} 这样做是做了 我们平时$()时 就能执行init里的方法; 不用每次都要先调用init; 如何实现? jquery.property.init.property = jquery.property; 这样init 不仅有自身方法也有jquery的所有方法
  • null 只与 null和undefined 相等, 其他都是false
  • typeof Nan == ‘number’ 所以要用isNan 和 isFinite 来判断是不是数字
  • 判断类型建议使用 [].toString.call(new Date) {}.toString.call(new Date) 获取更新详细的结果
  • obj.constructor.prototype.hasOwnProperty(‘isPrototypeOf’) 对象独有的属性

mouseover mousein 结构嵌套时冒泡解决方法

  • mouseover mousein 在结构嵌套的时候会有冒泡传递事件影响有些需求事件, 可以换成mouseleave mouseenter
  • 其他解决方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    //解决方法
    <script>
    var oDiv1 = document.getElementById('div1');
    var oDiv2 = document.getElementById('div2');
    var oSpan1 = document.getElementById('span1');
    oDiv1.onmouseover = function(ev){
    var ev = ev || window.event;
    var a = this;
    var b = ev.relatedTarget;
    if( !elContains(a, b) && a!=b ){
    oSpan1.innerHTML += '1';
    }
    };
    oDiv1.onmouseout = function(ev){
    var ev = ev || window.event;
    var a = this;
    var b = ev.relatedTarget;
    if( !elContains(a, b) && a!=b ){
    oSpan1.innerHTML += '2';
    }
    };
    function elContains(a, b){ //两个元素是否是嵌套关系
    return a.contains ? a != b && a.contains(b) : !!(a.compareDocumentPosition(b) & 16);
    }
    </script>
    <body>
    <style>
    #div1{ width:200px; height:200px; background:red;}
    #div2{ width:100px; height:100px; background:yellow;}
    </style>

    <div id="div1">
    <div id="div2"></div>
    </div>
    <span id="span1">11111111111</span>
    </body>

trigger() 与 triggerHandler的区别

  • trigger 会触发对应事件的回调函数, 并且触发事件的默认行为(比如focus 光标会获取焦点)
  • triggerHandler 会触发对应事件的回调函数, 但不会触发事件的默认行为

    $().prop$().attr的区别

  • $("#id").prop 实现的原理是
    • 设置值时: document.getElementById('id')['name'] = 'name'
    • 获取值时: document.getElementById('id')['name']
  • $("#id").attr 实现的原理是
    • 设置值时: document.getElementById('id').setAttribute = 'name'
    • 获取值时: document.getElementById('id').getAttribute
  • 当对document等对象(没有setAttrite方法), 建议使用prop(jquery做了兼容)
  • 当获取自定义或者设置自定义属性时, prop在某些浏览器上无法实现
  • removeProp在某些浏览器上无法删除原生属性

浏览器内存泄漏

1
2
3
4
var div1 = document.getElementById('div')[0];
var div2 = document.getElementById('div')[1];
div1.name = div2;
div2.name = div1;

$的 attr, prop, data3个方法的区别

  • data不会有浏览器内存泄漏, 其他2个会有
  • data适合挂载大量数据
  • data是利用一个中间件将对象和属性联系起来的, 不是直接将属性挂载在对象上, 这样就避免了内存泄漏, 和挂载大量数据时影响效率

JavaScript坑总结

作用域

  • 通过函构造函数创建的函数的[[scope]]属性总是唯一的全局对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    var x = 10;

    function foo() {

    var y = 20;

    function barFD() { // 函数声明
    alert(x);
    alert(y);
    }

    var barFE = function () { // 函数表达式
    alert(x);
    alert(y);
    };

    var barFn = Function('alert(x); alert(y);');

    barFD(); // 10, 20
    barFE(); // 10, 20
    barFn(); // 10, "y" is not defined

    }

    foo();
  • Function构造不同的是eval()可以干扰作用域链,而Function()更安分守己些。不管你在哪里执行 Function(),它只看到全局作用域。所以其能很好的避免本地变量污染

    1
    2
    3
    4
    5
    var global = “global";
    (function () {
       var local = 1;
       Function("console.log(typeof local, global);")(); // undefined global
    }());
  • 如果一个属性在对象中没有直接找到, 会现在全局查找, 如果还没找到,查询将在原型链中继续

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function foo() {
    alert(x);
    }

    Object.prototype.x = 10;

    foo(); // 10

    // -----

    function foo() {
    alert(x);
    }

    var x = 12
    Object.prototype.x = 10;

    foo(); // 12
  • with 声明临时调整的变量, 在跳出with后不会生效, 其他变量在with中修改有效

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var x = 10, y = 10;

    with ({x: 20}) {

    var x = 30, y = 30;

    alert(x); // 30
    alert(y); // 30
    }

    alert(x); // 10
    alert(y); // 30

this指向谁

1
2
3
4
5
6
7
var foo = {
bar: function(){return this.a},
a: 1
}
var f = foo.bar();
foo.bar() // 1
f() //undefined
  • foo.bar() 调用者是foo 所以this指向foo的a
  • f() 调用者是window 所以是undefined ===> window.f()
1
2
3
4
5
6
7
8
9
10
11
12
var foo = {
bar: function () {
console.log(this);
}
};

foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo

(foo.bar = foo.bar)(); // global?
(false || foo.bar)(); // global?
(foo.bar, foo.bar)(); // global?

是不是实例

1
2
3
4
5
function f(){}
new f() instanceof f // true

function f(){ return f;}
new f() instanceof f // false

函数的length

  • 函数的形参就是length的值
  • arguments 是实参的个数
    1
    2
    function test(a, b, c){}
    test.length//3

with关键字

  • with的作用就是临时改变作用域
  • __proto__属性临时指向Object.prototype
  • 性能较差
    1
    2
    3
    4
    5
    6
    7
    8
    var x = 20, y = 20, z = 30;
    var obj = {
    x: 1,
    y: 2,
    }
    with(obj) {
    console.log(x, y, z) //1, 2, 30
    }

内存泄漏

  • 这个多余的g函数就死在了返回函数的闭包中了,因此内存问题就出现了。这是因为if语句内部的函数与g是在同一个作用域中被声明的。这种情况下 ,除非我们显式断开对g函数的引用,否则它一直占着内存不放。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var f = (function(){
        var f, g;
        if (true) {
          f = function g(){};
        }
        else {
          f = function g(){};
        }
        // 设置g为null以后它就不会再占内存了
        g = null;
        return f;
      })();

Object.preventExtensions() Object.seal() Object.freeze()的区别

Object.preventExtensions() 对象不可扩展, 即不可以新增属性或方法, 但可以修改/删除
Object.seal() 在上面的基础上,对象属性不可删除, 但可以修改
Object.freeze() 在上面的基础上,对象所有属性只读, 不可修改

数组

  • slice()方法实现的是浅拷贝
    • MDN上的解释: For object references (and not the actual object), slice copies object references into the new array. Both the original and new array refer to the same object. If a referenced object changes, the changes are visible to both the new and original arrays.
  • push()

    • 对一个数组push数组, 应该先使用concat或者slice数组在push进去, 不然只是引用关系
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      var arr = [{a: 1}]
      var arr2 = arr.slice()
      arr[0].a = 2
      arr2[0].a // 2
      //---
      var arr = [1, 2];
      var allArr = [];
      allArr.push(arr);
      arr.push(3);
      allArr[0] // [1, 2, 3]

      var arr = [1, 2];
      var allArr = [];
      allArr.push(arr.concat();
      arr.push(3);
      allArr[0] // [1, 2]
  • 数组引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var arr = [1, 2, 3];
    //这样不进行拷贝 只是引用关系
    var arr2 = arr;
    arr2.push(4);
    console.log(arr) //1, 2, 3, 4
    console.log(arr2) //1, 2, 3, 4

    //这样arr 与 arr2就没有引用关系了
    arr2 = [1, 2, 3];
    console.log(arr) //1, 2, 3, 4
    console.log(arr2) //1, 2, 3
  • Array() 与 Array.of() 的区别

    1
    2
    3
    4
    5
    Array(2) // [, ,]
    Array(2,3) //[2, 3]

    Array.of(2) //[2]
    Array.of(2,3 ) // [2, 3]
  • map与forEach 的区别

    • map会根据返回的值改变数组对应的值
    • forEach只是单纯的执行一遍函数不会修改数组
      1
      2
      3
      4
      5
      6
      var arr = [1, 2, 3, 4, 5];
      function test(num) {
      return num *2;
      }
      arr.forEach(test); //[1, 2, 3, 4, 5]
      arr.map(test); // [2, 4, 6, 8, 10]

正则

  • (?=) 前向声明

    1
    2
    3
    4
    //需求: 匹配到ab 才能将a换成* 这时就需要用到前向声明了
    var str = 'abacadab';
    var reg = /a(?=b)/g
    str.replace(reg, '*')//*bacad*b
  • (?!) 反前向声明

    1
    2
    3
    4
    //需求: 匹配到a 但后面不是b的情况下 才能将a换成* 这时就需要用到反前向声明了
    var str = 'abacadab';
    var reg = /a(?!b)/g
    str.replace(reg, '*')//ab*c*dab
  • 前向声明和反前向声明的应用

    1
    2
    3
    4
    5
    6
    function test4(str) {
    // 声明后面的 (?!\b) 不能以空格开头 必须是3位数字为一组一直到结束
    var reg = /(?=(?!\b)(\d{3})+$)/g;
    return str.replace(reg, ',')
    }
    test('1234567') //1,234,567

React Native快速入门学习计划

  1. 学习JavaScript基础语法(有Java基础可跳过) 时间: 3天
  2. 学习ES6最新语法糖 (可以先跳过, 不会了再查) 时间: 3天
  3. 搭建React Native运行环境 时间: 1天
  4. 了解RN的propsstate的区别 时间: 1天
  5. 了解RN的生命周期, View组件, Text组件, Image组件, flex布局, Style属性, touchablehighlight组件 时间: 3天
  6. 了解promise用法 时间: 1天
  7. 了解网络请求 时间: 1天
  8. 了解ScrollView组件 时间: 2天
  9. 了解Navigator组件 时间: 2天
  10. 了解WebView组件 时间: 2天
  11. 了解与原生桥接 时间: 3天
  12. 了解AsyncStorage 时间: 1天

了解以上足以入门ReactNative了