《你不知道的javascript》学习笔记

严格模式

ES5中引入严格模式,严格模式与非严格模式的一些区别:

  1. 严格模式下,禁止隐式或自动的创建全局变量;
  2. 严格模式下,禁止使用with函数,会根据所传对象创建一个全新的作用域,有性能消耗,不建议使用;
  3. 严格模式下,eval(…)在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域,有性能消耗,不建议使用;
  4. 严格模式下,不能使用默认绑定,function中this是undefined,非严格模式是全局对象;
  5. 严格模式下,对一个不可写的属性进行修改会时报错,非严格模式下,只是不起作用,但不会报错
  6. 严格模式下,八进制就不再允许使用前缀0表示
  7. ES6 的尾调用优化只在严格模式下开启,正常模式是无效的
  8. 类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用

常见异常

  1. ReferenceError 同作用域判别失败相关
  2. TypeError 则代表作用域判别成功了,但是对结果的操作是非法或不合理的

作用域欺骗

以下几种作用域欺骗的函数,不建议使用,不仅使作用域混乱,还会带来性能消耗

  1. eval函数的调用会在运行时修改词法分析的作用域:

    function foo(str, a) { 
        eval( str ); // 欺骗! console.log( a, b );
    }
    var b = 2;
    foo( "var b = 3;", 1 ); // 1, 3
  1. setTimeout(…)/setInterval(…)/new Function(…)三个函数的参数也可以传入字符串,造成和eval的一样的效果-》修改作用域;

  2. with函数,在运行时凭空创建一个作用域,也很容易出现作用域欺骗:


    function foo(obj) { 
        with (obj) {
             a = 2; 
            
        }
    }
    var o1 = {  
        a: 3
    };
    var o2 = {
        b: 3
    };
    foo( o1 );
    console.log( o1.a ); // 2
    foo( o2 );
    console.log( o2.a ); // undefined
    console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!

块作用域

  1. let
  2. const
  3. with
  4. try -catch结构中的catch

声明提升

javascript 在编译时进行声明处理,在运行时进行变量赋值处理:

a = 2;
var a;
console.log(a); //代码会正确输出2

函数声明会被提升,但是函数表达式不会被提升:

//函数声明被提升,执行结果正确
foo();
function foo(){
    console.log('hello world');
}

//函数表达式没有被提升,执行结果报错
foo(); //此时foo还是undefined,所以报引用错误
var foo = function(){
    console.log('hello world');
}

箭头函数

箭头函数中 this 指针是词法作用域,定义它时所在的对象,而非执行它时所在的对象:

//第一段程序
var a = 1;
var obj = {
    a: 2,
    f1: () => console.log('f1.this.a:', this.a),
    f2: function(){ console.log('f2.this.a:', this.a)}
};
obj.f1(); //输出为1,因为f1定义时所在的词法作用域是全局,这里要注意字面值定义对象,本身不具备词法作用域,会穿透
obj.f2(); //输出是2,因为f2执行时所在的词法作用域obj
obj.f1.call({a: 3}); //输出是1,由定义时决定,执行时不会改变
obj.f2.call({a: 3}); //输出是3,执行时动态绑定了作用域{a:3}

//第二段程序
var a = 1;
var F = function(){
    this.a = 2;
    this.f1 = () => console.log('f1.this.a:', this.a),
    this.f2 = function(){ console.log('f2.this.a:', this.a);}
};
var f = new F();
f.f1(); //输出为2,因为定义时所在的作用域是F
f.f2(); //输出为2,因为运行时绑定了f
f.f1.call(null); //输出是2
f.f2.call(null); //输出为1,绑定为null,表示全局作用域
f.f1.call({a: 3}) //输出是2
f.f2.call({a: 3}) //输出是3,绑定了{a: 3}这个作用域

//第三段程序
var a = 1;
var f1 = () => console.log('f1.this.a:', this.a);
var f2 = function(){ console.log('f2.this.a:', this.a); }
var F = function(){
    this.a = 2;
    this.f1 = f1,
    this.f2 = f2
};
var f = new F();
f.f1(); //输出为1,和第二段程序不同的是,f1的定义是在全局的,并不是在F中的,F中只是引用了f1
f.f2(); //输出为2,因为运行时绑定了f
f.f1.call(null); //输出是1
f.f2.call(null); //输出为1,绑定为null,表示全局作用域
f.f1.call({a: 3}) //输出是1
f.f2.call({a: 3}) //输出是3,绑定了{a: 3}这个作用域

this

  1. this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用;
  2. 默认绑定:默认绑定指向全局对象(在非严格模式下);
  3. 隐式绑定:当函数引用有上下文对象时(即函数作为引用属性被添加到对象中),隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象;绑定至上下文对象的函数被赋值给一个新的函数,然后调用这个新的函数时,会丢失绑定,然后走默认绑定;
  4. 显式绑定:显式绑定的核心是JavaScript内置的call(…)和apply(…)方法,第一个参数是一个对象,他们会将this绑定到这个对象上,如果第一个参数时null或者undefined,会走默认绑定,这个时候要注意,如果函数中使用this,尽量不要使用默认绑定。
  5. new构造函数this绑定:使用 new 来调用 foo(…) 时,会构造一个新对象并把它绑定到 foo(…) 调用中的 this 上:
function foo(a) {
 this.a = a;
}
var bar = new foo(2);
console.log(bar.a);     // 2

  1. this绑定判断顺序:
  • 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
    var bar = new foo()
  • 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。
    var bar = foo.call(obj2)
  • 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
    var bar = obj1.foo()
  • 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。
    var bar = foo()
  1. 软绑定:当指定了绑定方式(隐式绑定,new绑定,显式绑定)则使用该绑定方式,否则使用softBind函数制定的对象进行绑定:
if (!Function.prototype.softBind) {
    Function.prototype.softBind = function(obj){
        var fn = this;
        //fun也是对象,用fun调用sortBind时,绑定函数本身;
        console.log('fn:', fn);
        var curried = [].slice.call(arguments, 1);
        var bound = function(){
            return fn.apply(
                (!this || this === (global || window)) ? obj : this,
                curried.concat.apply(curried, arguments)
            );
        };
        bound.prototype = Object.create(fn.prototype);
        return bound;
    };
} 

类型和内置对象

javascript 有7种基本类型:
  • string
  • number
  • boolean
  • null // typeof null 返回 “object”,但是这里我们认为它是单独的一个类型,看怎么看待
  • undefined
  • object // typeof 一个函数返回"function",但是function不是基本类型,他是object的子类型
  • symbol // ES6新出来的类型
typeof的安全防范机制

如果一个变量没有声明过直接使用会报错,但是可以使用 typeof 操作符进行判断,返回undefined,可以作为一种检测变量未定义的安全防范机制,防止报错

值类型
  • 数组:
//工具函数slice将类数组转换为数组
function foo(){
    let arr = Array.prototype.slice(arguments); //可以用ES6中的Array.from实现了
}
  • 字符串
//字符串不是字符的数组,是不可变的
let arr = ['0', '1', '2'];
let str = "012";
str[1] = "一";     //有些浏览器该操作会报错
arr[1] = "一";
console.log(str);  // "012"
console.log(arr);  // ['0', '一', '2']

//JSON.stringify函数
JSON.stringify(null)  // "null"
JSON.stringify(NaN)  // "null"
JSON.stringify(undefined)  // undefined
JSON.stringify(12)  // "12"
JSON.stringify("12")  // ""12""
//每个对象可以定义自己的toJSON函数,这样可以改变JSON.stringify的输出
//JSON.stringify的第二个可选参数replacer,可以根据key和value的值修改输出,这个key/value是深层次的遍历
//JSON.stringify的第三个可选参数space,可以是指定输出的设定格式

  • 数字
//javascript中没有整数的概念,整数其实就是没有带小数的十进制数
console.log(42.0) // 输出为42,在传输的过程中,会自动删除末尾的0
Number.isInteger(3) // true
Number.isInteger(3.0) // true
Number.isInteger(3.1) // false

// void 操作符,使表达式返回undefined
console.log(void 1, 2); // undefined 2

// NaN -> 不是数字的数字,一般是在参与数字运算时,表示返回一个无效的数字
typeof NaN // "number"
NaN === NaN // false , javascript中唯一一个非自反的值
isNaN // 检查是否NaN或者不是数字,有歧义
Number.isNaN //检查是否是NaN,没有歧义

//~符号与-1结合使用
if(~arr.indexOf(a)){}

//parseInt和Number的区别
Number("42")   //42
parseInt("42") //42
Number("42b")  // NaN
parseInt("42b") // 42
  • symbol
//符号不支持隐式强制类型转换为字符串,符号的显式和隐式强制类型转换为字符串都为true
let s1 = Symbol('hello world');
String(s1); // "Symbol(hello world)"
s1 + '';    // TypeError
  • == 和 === 不等号的区别
==”允许比较过程中进行强制类型转换,“===”不允许比较过程中进行强制类型转换

javascript 中有9种内置对象
  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

对象

  1. 对象属性描述符
  • Writeable:表示是否可写
  • Configurable:可配置属性,当Configurable = false时,还禁止该属性被删除
  • Enumerable:for-in可枚举属性
  • getter和setter属性:改变赋值和读取对象属性的行为
//两种方法定义getter属性
var myObject = {
    // 给 a 定义一个 
    get a() {
        return 2; 
    }
};
Object.defineProperty( myObject, // 目标对象 "b", // 属性名
    {
        get: function(){ return this.a * 2 },
        enumerable: true
    }
);

//两种方法定义setter属性
var myObject = {
    // 给 a 定义一个 
    set a() {
        return 2; 
    }
};
Object.defineProperty( myObject, // 目标对象 "b", // 属性名
    {
        set: function(){ return this.a * 2 },
        enumerable: true
    }
);
  • Symbol.iterator属性:可以使用for … of遍历对象中属性的值,数组可以直接使用for … of遍历元素的值,而普通对象没有Symbol.iterator属性,需要自己定义该属性,实现for … of操作:
var myObject = { 
    a: 2,
    b: 3 
};
Object.defineProperty( myObject, Symbol.iterator, {
    enumerable: false,
    writable: false,
    configurable: true,
    value: function() {
        var o = this;
        var idx=0;
        var ks = Object.keys( o );
        return {
            next: function() {
                return {
                    value: o[ks[idx++]],
                    done: (idx > ks.length)
                };
            }
        };
    }
});

// 手动遍历 myObject
var it = myObject[Symbol.iterator](); 
it.next(); // { value:2, done:false } 
it.next(); // { value:3, done:false } 
it.next(); // { value:unde ned, done:true }
// 用 for..of 遍历 myObject 
for (var v of myObject){ 
    console.log( v );
}
  1. Object系列函数:
  • Object.preventExtensions() -> 禁止一个对象添加新属性并且保留已有属性,可以重新配置属性
  • Object.seal() -> 密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性
  • Object.freeze() -> 密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性,也不能修改属性的值
  • Object.keys(…) 会返回一个数组,包含所有可枚举属性,只查找对象本身
  • Object.getOwnPropertyNames(…) 会返回一个数组,包含所有属性,无论它们是否可枚举,只查找对象本身
  • Object.getPrototypeOf(a) 获取对象a的原型链
  • Object.create(…)会凭空创建一个“新”对象并把新对象内部的[[Prototype]]关联到你指定的对象
//Object.create的大致实现原理:
if (!Object.create) { 
    Object.create = function(o) {
        function F(){} 
        F.prototype = o; 
        return new F();
    }; 
}
  1. object对象实例系列函数:
  • in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中;
  • for … in 操作会遍历对象的所有属性包括原型链上的属性;
  • object.hasOwnProperty(…) 只会检查属性是否在对象中;
  • object.propertyIsEnumerable(…) 会检查给定的属性名是否直接存在于对象中(而不是在原型链 上)并且满足enumerable:true
  • b.isPrototypeOf© 判断对象b是否为c的原型链
  • c instanceof B 判断的是B.prototype是否存在c的原型链之中,这个方法是站在类的角度来判断,这个方法在有些情况下并不是很准确:
function A(){}
var a = new A();
console.log(a instanceof A)  //true
var c = A.prototype;
var b = {};
A.prototype = b;
console.log(a instanceof A) //false
function C(){}
C.prototype = c;
A.prototype = c;
console.log(a instanceof C) //true
console.log(a instanceof A) //true

原型和prototype属性:

什么是[[prototype]]属性
  • javascript 中对象有一个特殊的内置属性 [[Prototype]],是对其它对象的一个引用,我们称之为对象的原型链
  • [[Prototype]] 不能直接访问,在 ES6 中可以通过函数 Object.getPrototypeOf() 来获取对象原型链
  • 在 ES6 之前,可以使用 _proto_ 属性来获取某个对象的原型链和修改某个对象的原型链,但不是所有浏览器都支持
  • 新建一个普通的文本对象,它的原型链都会指向 Object.Prototype
  • 可以通过 Object.create(ES5)和 Object.setPrototypeOf (ES6) 设置关联某个对象的原型链,具体什么区别看下文
什么是prototype属性
  • javascript 中所有的函数对象都会默认拥有一个 Prototype 公有且不可枚举的属性,也指向另一个对象,我们称之为函数的原型
  • 函数的原型可以直接访问 Fun.prototype
  • Object.Prototype:所有普通的原型链最终都会指向Object.Prototype,包含了javascript中许多通用的功能: toString函数/valueOf函数/hasOwnPerperty函数等
对象属性的查找和设置
  • 属性的查找
console.log(myObject.foo)
//先查找当前对象中有没有foo属性,如果继续查看原型链上的对象有没有该属性,直到查到该属性或者没有原型链为止
  • 属性的设置和屏蔽
myObject.foo = "bar";
/*
1. 如果 myObject 对象中包含名为 foo 的普通数据访问属性,这条赋值语句只会修改已有的属性值;
2. 如果 foo 不是直接存在于 myObject 中,[[Prototype]] 链就会被遍历,类似 [[Get]] 操作,如果原型链上找不到 foo,foo 就会被直接添加到 myObject上;
3. 如果 foo 不直接存在于myObject 中而是存在于原型链上层时有三种情况:
   (1) 如果在 [[Prototype]] 链上层存在名为foo,但没有被标记为只读(writable:false),那就会直接在myObject中添加一个名为foo的新属性,它是屏蔽属性;
   (2) 如果在 [[Prototype]] 链上层存在foo,但是它被标记为只读(writable:false),那么无法修改已有属性或者在myObject上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则这条赋值语句会被忽略,总之不会发生屏蔽;
   (3) 如果在 [[Prototype]] 链上层存在 foo 并且它是一个 setter,那就一定会调用这个setter,foo不会被添加到(或者说屏蔽于)myObject,也不会重新定义foo这个setter;
   (4) 如果你希望在第二种和第三种情况下也屏蔽foo,那就不能使用 = 操作符来赋值,而是使用Object.defineProperty(..)来向myObject添加foo;
*/

创建原型链的几种方法
  • 方法1:使用函数的原型
function Fun(){}
//此时a的原型链就是Fun的原型,该方法会有一定的副作用,比如给a添加属性,修改状态,写日志等,不过要根据使用场景来决定
let a = new Fun();
Object.getPrototypeOf(a) === Fun.prototype   // true
Foo.prototype.constructor === Foo; // true
a.constructor === Foo; // true
  • 方法2:Object.create

ES5提供的,方法简短,可读性高

// Object.create的其实基于函数原型实现的:
Object.create = function(a){
    let Fun = function(){};
    Fun.prototype = a;
    return new Fun();
}
  • 方法3:Object.setPropotypeOf

ES6之后提供的函数,相比Object.create,性能会更好,没有中间变量的开销

let a = Object.setPropotypeOf(b);

  • 方法4:proto 属性

可以直接使用 proto 属性设置对象的原型链,但是有些浏览器不支持,兼容性不够好

原型链的判断
  • a instanceOf Foo:判断函数Foo的prototype是否在a的原型链上
  • b.isPrototypeOf(a): 判断 b 是否在 a 的原型链上

原型关系图

在这里插入图片描述

ES6 中的 class

  • class 并不像传统面向对象语言一样,是一种复制行为,而是一种实时关联委托机制,如果你不小心修改了父类中的一个函数,那么所有的子类和实例都会受到影响
  • class 存在屏蔽问题
class C {
    constructor(id){
        this.id = id; //此时id属性屏蔽了id函数
    }
    id(){
        console.log('id:', id);
    }
}

let c = new C('c1');
c.id()   //TypeError

  • super 函数是静态绑定的,没法实现动态绑定功能

ES6 特殊语法

解构和默认值
  • ES6 参数默认值会使 arguments 数组和对应的命名参数值之间出现偏差
let esFun = function(a = 3, b = 4){
    console.log('aguments.length:', arguments.length);
    console.log('a:', a);
    console.log('b:', b);
}

esFun();  //aguments.length: 0 a:3 b:4
esFun(1); //aguments.length: 1 a:1 b:4
esFun(1, 2); //aguments.length: 2 a:1 b:2

其它

  1. 关于console.log的二三事
//如果对象里包含了inspect函数,inspect函数会控制console.log的打印
let obj = {
    inspect: function(){
        return {
            name: "zhangdianpeng",
            email: "zdp@163.com"
        }
    }
}

console.log('obj:', obj);  //=>输出是inspect函数的返回

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值