JS 基础知识碎片

8 种基本数据类型


  • Number: 数值类型。范围是±(253-1);特殊值有Infinity(正无穷,其实是大过最大值), -Infinity(负无穷,其实是小过最小值)和 NaN(非数值)。
  • String: 字符串类型。
  • Object: 对象类型。
  • Boolean: 布尔类型 true or false
  • null: 特殊的对象,表示空,已定义未赋值。
  • undefined: 未定义
  • BigInt: 表示任意长度的整数。通过在数值末尾加 n来定义,比如 const bigInt = 1234567890123456789012345678901234567890n;
  • Symbols: 生成唯一标示符。

typeof 用于检测变量的数据类型:

  • 两种使用方式: typeof x or typeof(x)
  • 返回类型的字符串, 比如 "string"
  • typeof null 返回 "object" null 被认为是一个 特殊 的对象( 空对象 )。

BigInt 注意兼容性: Firefox/Chrome/Edge/Safari都已支持, IE尚不支持。

作用域(scope)之声明提前(hoisting)

  • js 只有函数作用域,没有块级作用域。
  • js 的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。
  • 这意味这变量在函数体内声明之前就已经可用,这个特性被非正式的称为声明提前(hoisting)。
var scope = "global"
function f() {
  console.log(scope);  // 输出 undefined 而非 global
  var scope = "local";
  console.log(scope);  // 输出 local
}

上述函数相当于


function f() {
  var scope;  // 自动的 声明提前
  console.log(scope);
  scope = "local";
  console.log(scope);
}

toString() 和 valueOf()

  • 所有的对象继承了两个转换方法:toString()valueOf()
  • toString() 不同的对象类型表现不同,比如数组返回逗号拼接的字符串,日期类型返回一个可读的日期事件字符串等。
  • valueOf() 如果对象存在原始值,就返回原始值。对象是复合值,大多数时候无法真正表示为一个原始值,就返回其本身。
[1,2,3].toString();  // "1,2,3"
new Date(2010,0,1).toString();  // "Fri Jan 01 2010 00:00:00 GMT+0800 (中国标准时间)"
new Date(2010,0,1).valueOf();  // 1262275200000

in 运算符

var obj = { x: 1, y: 2 }
"x" in obj; // => true
"z" in obj; // => false
"toString" in obj; // => true: 所有对象继承了toString()方法

instanceof 运算符

  • A instanceof B : 左操作数A为一个对象,右操作数B标示对象的类型。
  • 如果AB的实例就返回true,否则返回false
var d = new Date();
d instanceof Date;     // => true
d instanceof Object;   // => true : 所有对象都是 Object 的实例
d instanceof Number;   // => false
var a = [1, 2, 3];
a instanceof Array;    // => true
a instanceof Object;   // => true
function f() { };
f instanceOf Function;  // => true

创建对象

  • 对象直接量 :常用方式。
var empty = {};       // 空对象
var point = {x: 0, y: 0};
var book = {
  "main title": "Javascropt", // 属性名有空格必须使用字符串表示
  "sub-title": "The Definitive Guide",  // 属性名有连字符必须使用字符串表示
  for: "all audiences"
}
  • new:new运算符创建并初始化一个新对象,new后面跟随一个函数调用,这个函数其实是一个构造函数(constructor)。
var empty = new Object();       // 同 {}
var point = new Object({x: 0, y: 0});
var a = new Array(); // 同 []
var b = new Date();
  • Object.create(proto, propertiesObject)ES6定义的方法,它创建一个对象.其中proto(必须参数)是这个对象的原型。
    propertiesObject是可选参数,作用就是给新对象添加新属性以及描述器。具体可参考 Object.defineProperties() - mdn 的第二个参数。新添加的属性是新对象自身具有的属性也就是通过hasOwnProperty() 方法可以获取到的属性,
// 创建一个没有原型的对象
var o1 = Object.create(null);  
// 同 {} 和 new Object()
var o2 = Object.create(Object.prototype); 
o2.a = 1;
// o3._propo_ = o2 继承了o2, 也继承了o2的属性a;增加了属性 b=1;
var o3 = Object.create(o2, {b: { vaue: 1, writable: true}}); 
o3; // print {b:1} :自🈶️属性
o3.a; // print 1: 获取对象的属性,如果自有属性没有,会继续在对象继承的原型链中(o3>o2>Object.prototype)查找。

自己实现一个 Object.create():

Object.myCreate = function (obj, properties)  {
  var F = function ()  {}
  F.prototype = obj
  if (properties) {
     Object.defineProperties(F, properties)
  }
  return new F()
}

Object.myCreate({}, {a: {value: 1}})     // {a: 1}

对象动态属性

// 设置
var selfProp = 'other';
var index = 1;
var book={
  "main title": "", // 属性名有空格必须使用字符串表示
  "sub-title": "",  // 属性名有连字符必须使用字符串表示
  auther: "",
  [selfProp + index]: ""   // 动态属性通过 [] 设置。 
}
// 读取 
var auther = book.auther;
var title = book["main title"]; // 属性名有空格,连字符的属性通过 [] 获取。
var other = book[selfProp + index]; // 动态属性通过 [] 获取。

删除属性

  • delete只能删除自由属性,无法删除继承属性。
  • delete无法删除通过var定义的全局变量。
delete book.auther;              // true
delete book["main title"];       // true
var obj = Object.create({x: 1}); 
delete obj.x;                    // true 但是并没有删除继承属性x。
var global1 = 1;                 // 定义全局变量,用var
this.global2 = 2;                // 定义全局变量
delete global1;                  // 无法删除
delete global2;                  // 可删除

检测属性的几种方式

  • in 运算符
  • .property !== undefined的形式
  • hasOwnProperty() ( 只能检测自有属性 )

枚举对象属性

  • for/in 运算符: 可枚举自有属性和继承属性
  • Object.keys(obj):只能枚举自有属性

Object对象常用方法

  • Object.assign();
  • Object.create();
  • Object.keys();
  • Object.values();
  • Object.defineProperty();
  • Object.entries();
  • 其他-MDN

对象原型判断

  • isPrototypeOf方法(推荐)
  • _proto_属性
var obj = {};
var newObj = Object.create(obj);
obj.isPrototypeOf(newObj);  // true
newObj._proto_ === obj; // true

获取对象类属性(class attribute)

  • typeof 操作符:
typeof null;         // => "object"
typeof undefined;    // => "undefined"
typeof true;         // => "boolean"
typeof 100;          // => "number"  NaN也是
typeof "abc";        // => "string"
typeof function(){}; // => "function"
typeof 任意内置对象;  // => ""object
  • 利用Object.prototype.toString() 返回 [object class]的特性,实现一下方法:
function classof(0) {
  if (o === null) return 'null';
  if(o === undefined) return 'undefined';
  return Object.prototype.toString.call(o).slice(8,-1);
}

classof(1); // => 'Number'
classof(""); // => 'String'
classof(false); // => 'Boolean'
classof({}); // => 'Object'
classof([]); // => 'Array'
classof(/./); // => 'Regexp'
classof(new Date()); // => 'Date'
classof(Window); // => 'Window'
function f() {};
classof(new f()); // => 'Object'

数组须知

  • 数组索引必须为非负整数(非负整数的字符串也可以)。使用其他类型来索引数组,只能作为数组的属性。
var a = new Array(10) // => length = 10 
a["1000"] = 1; // => length = 1001  ( [0,1,...1000] )
a[-1] = -1; // => length 不变,数组多一个“-1”属性
  • 数组也是对象,可以使用对象的各种方法和操作。
//eg
var a = new Array(10)
Object.defineproperty(a,'length',{writable: false}) // 让数组length不可变
  • delete 不会改变数组的length,只是让某个 索引位 没有值。

Get/Post 请求须知

  • 请求数据大小的限制并不是HTTP协议限制,而是浏览器的限制。
  • get适合查找数据,post适合添加/修改数据。所以在缓存策略上,get请求可以被缓存,post请求不会被缓存。

为什么频繁调用 element.getboundingclientrect() 可能引发重绘/回流?

浏览器都会优化重绘和回流的操作。浏览器会把所有会引起回流、重绘的Dom操作放入一个队列中,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

另外,当我们取一些属性值时(offsetWidth、clientWidth、width)等,只为了取到正确的值,浏览器可能提前执行 flush 队列,即便是队列里的操作不影响所取的值。

ES6 定义对象方法时,还可以省去 function 关键字。

{
  reducers: {
    add() {}  // 等同于 add: function() {}
  },
  effects: {
    *addRemote() {}  // 等同于 addRemote: function*() {}
  },
}

Set集合 & Map字典

Set 操作方法:

  • add(value):新增,相当于 array里的push。
  • delete(value):存在即删除集合中value。
  • has(value):判断集合中是否存在 value。
  • clear():清空集合。

Map 操作方法:

  • set(key, value):向字典中添加新元素。

  • get(key):通过键查找特定的数值并返回。

  • has(key):判断字典中是否存在键key。

  • delete(key):通过键 key 从字典中移除对应的数据。

  • clear():将这个字典中的所有元素删除。

  • 共同点:集合、字典 可以储存不重复的值。

  • 不同点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存

页面加载多个JS的处理过程

step 1. 读入第一个代码块。

step 2. 做语法分析,有错则报语法错误(比如括号不匹配等),并跳转到step5。

step 3. 对var变量和function定义做“预编译处理”(永远不会报错的,因为只解析正确的声明)。

step 4. 执行代码段,有错则报错(比如变量未定义)。

step 5. 如果还有下一个代码段,则读入下一个代码段,重复step2。

step6. 结束。

HTTPS加密为啥需要CA认证

方案一:对称加密

加密解密使用同一个密钥。
存在密钥协商的问题(客户端和服务器同步密钥的时候很容易被截获)。

方案二:非对称加密

公钥/私钥互为锁和钥匙。如果 公钥 在服务器发送给客户端时被窃取。那么服务器私钥加密的内容会被坏人解密。不过,客户端发送的公钥加密过的数据,坏人没有私钥没法解密。另外,非对称加密性能是很差的,因为支持双向加解密,复杂度和密钥长度都需要很大,否则容易被暴力破解。

方案三:改进方案

  • 方案:客户端获取公钥后生成对称密钥,然后用公钥加密后给服务器,服务器用私钥解密得到对称密钥,后续就进行对称加密即可。
  • 问题:如果坏人一开始就窃听了公钥,然后发送假的公钥(当然坏人有对应的私钥)给客户端。那么客户端发给服务器的对称密钥会被第三方解密,然后第三方用真的公钥加密给服务器。这样,后续的通信中,第三方就能获取所有的信息,毕竟得到了对称密钥。

为了做到绝对的安全,CA认证(数字证书+数字签名)诞生。具体流程如下:

  1. 服务器需要提交服务器站点的信息如域名、公司名称、公钥等给CA机构去申请和购买数字证书。CA机构就是数字证书颁发的权威机构,负责颁发证书以及验证证书的合法性。

  2. CA机构在给服务器颁发证书的时候,除了数字证书,还会根据 数字证书 + hash算法(MD5)计算出摘要,CA机构自己的私钥对摘要进行加密形成数字签名一并发给服务器。

  3. 服务器在与客户端通信的时候,就会将数字证书和数字签名出示给客户端。客户端也能拿到浏览器内置的CA机构的公钥。

  4. 客户端使用CA公钥解密数字签名得到摘要,再通过 数字证书 + hash算法计算出摘要。如果两个摘要一致,那就证书有效,否则证书被篡改。因为CA机构的私钥坏人得不到。

  5. 客户端生成对称密钥,用从证书拿到服务器的 公钥 加密后发给服务器,服务器用 私钥 解密拿到密钥。因为客户端拿到的肯定是服务器正确的公钥,所以没有方案三存在的问题。

  6. 密钥协商完成,对称加密传输数据。

数组解构赋值交换元素

等号的左右两边模式相同,就会将右边的值赋给左边的变量。

[array[index1],array[index2]] = [array[index2],array[index1]];

JSBridge 原理

  • JavaScript 调用 Native 推荐使用 注入 API 的方式。通过 WebView 提供的接口,Native 向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。
  • Native 调用 JavaScript 则直接执行拼接好的 JavaScript 代码即可。
//js具体实现:
(function () {
    var id = 0,
        callbacks = {},
        registerFuncs = {};

    window.JSBridge = {
        // 调用 Native
        invoke: function(bridgeName, callback, data) {
            // 判断环境,获取不同的 nativeBridge
            var thisId = id ++; // 获取唯一 id
            callbacks[thisId] = callback; // 存储 Callback
            nativeBridge.postMessage({
                bridgeName: bridgeName,
                data: data || {},
                callbackId: thisId // 传到 Native 端
            });
        },
        receiveMessage: function(msg) {
            var bridgeName = msg.bridgeName,
                data = msg.data || {},
                callbackId = msg.callbackId, // Native 将 callbackId 原封不动传回
                responstId = msg.responstId;
            // 具体逻辑
            // bridgeName 和 callbackId 不会同时存在
            if (callbackId) {
                if (callbacks[callbackId]) { // 找到相应句柄
                    callbacks[callbackId](msg.data); // 执行调用
                }
            } elseif (bridgeName) {
                if (registerFuncs[bridgeName]) { // 通过 bridgeName 找到句柄
                    var ret = {},
                        flag = false;
                    registerFuncs[bridgeName].forEach(function(callback) => {
                        callback(data, function(r) {
                            flag = true;
                            ret = Object.assign(ret, r);
                        });
                    });
                    if (flag) {
                        nativeBridge.postMessage({ // 回调 Native
                            responstId: responstId,
                            ret: ret
                        });
                    }
                }
            }
        },
        register: function(bridgeName, callback) {
            if (!registerFuncs[bridgeName])  {
                registerFuncs[bridgeName] = [];
            }
            registerFuncs[bridgeName].push(callback); // 存储回调
        }
    };
})();
//作者:嫖桑 https://juejin.cn/post/6844903585268891662

语音播报

function  voiceAnnouncements(str){
//百度
    var  url = "http://tts.baidu.com/text2audio?lan=zh&ie=UTF-8&text=" + encodeURI(str); // baidu
    var  n = new  Audio(url);
    n.src = url;
    n.play();
}
voiceAnnouncements('你好,今天吃的什么?')

静态作用域 和 闭包

  • 因为JavaScript是静态作用域的,所以它内部环境中需要的变量在编译时就确定了,运行时不会改变;
  • 又因为JavaScript中,函数是一等公民,可以被调用,可以作为参数传递,可以赋值给变量,也可以作为函数返回值,所以它的运行时环境很容易变化;
  • 当函数作为另一个函数(外层函数)的返回值返回时,其外层函数中的变量已经从调用栈弹出,但是我们必须让内部函数可以访问到它需要的变量,因此运行时的环境和定义时的作用域之间就产生矛盾;
  • 所以我们把内部环境中需要的变量,打包交给内层函数(闭包函数),它就可以随时访问这些变量了,就形成了闭包。