sponsored links

關於小程式的一切,讀這一篇就夠了

原文來自公眾號-騰訊IMWeb前端團隊,@hope

1. 小程式發展史

1.1 Native App

在智慧機剛興起的時代,網路還不是很發達,網頁瀏覽速度也很慢,以文字為主。市面上的應用以 Native App 為主。

Native App 是基於 iOS 或者安卓的原生應用,特點是開發成本高,迭代慢,但是效能和體驗很好,訊息推送及時,比如 qq,微信等。

關於小程式的一切,讀這一篇就夠了

1.2 H5

2014 年 HTML5 完成標準定製,他的設計目的是為了在移動裝置上支援多媒體,引入了 Video、Audio 等技術。

在網頁上瀏覽影片變得很方便,特點是開發和釋出成本低,開啟方便,無需下載到本地,但是效能受瀏覽器的處理能力的限制,相較於原生 App 來說差了一些,訊息推送不及時。

1.3 Hybrid App

Hybrid App 就是混合式的 App,也就是在移動端原生應用的基礎上,透過 JSBrdige 等方法,訪問原生應用的 API 進行 JS 的互動,並透過 WebView 等技術實現 HTML 與 CSS 的渲染。

WebView 可以理解為嵌套了一個瀏覽器核心(比如 webkit)的移動端元件。

這種技術實現的應用一般都是跨平臺的,並且維護起來比較容易,效能介於 H5 和原生應用之間。

1.4 小程式

小程式是一種不需要下載安裝即可使用的應用,它實現了應用 “觸手可及” 的夢想,使用者掃一掃或者搜一下即可開啟應用。也體現了 “用完即走” 的理念,使用者不用關心是否安裝太多應用的問題。應用將無處不在,隨時可用,但又無需安裝解除安裝。—— 百度百科

自 2017 年 1 月微信小程式正式釋出以來,目前網際網路上已經有多種小程式:


微信小程式


百度智慧小程式


支付寶小程式


QQ 小程式


關於小程式的一切,讀這一篇就夠了


關於小程式的一切,讀這一篇就夠了


關於小程式的一切,讀這一篇就夠了


關於小程式的一切,讀這一篇就夠了


2017.1


2018.7


2018.9


2019.6

基於小程式幾乎相同的技術原理,以及小程式的方便快捷的特性,還衍生出了多款小程式,比如抖音小程式、快手小程式、京東小程式、美團小程式等,幫助各大廠商更好的為使用者提供便捷的服務。

2018 年微信小程式 “跳一跳” 爆火,記得當年食堂排隊打飯的時候很多同學都在玩,助力了微信小程式在使用者中的擴張,也激發了其他廠商開發小程式的熱潮。

關於小程式的一切,讀這一篇就夠了

2. 原理分析

2.1 雙執行緒模型

無論是微信小程式還是支付寶小程式還是百度智慧小程式等等,他們的總體架構都是基於雙執行緒的。

關於小程式的一切,讀這一篇就夠了

其中用於處理業務邏輯的 JS 程式碼執行在單獨的執行緒裡,渲染層(template、css)則執行在另外一個單獨的執行緒裡。

以微信小程式為例:

關於小程式的一切,讀這一篇就夠了

雙執行緒模型不同於單執行緒模型,邏輯層與渲染層的資料互動需要透過 JSBridge,二者是透過釋出訂閱,基於當前比較比較著名的 MVVM,來實現資料的雙向繫結的,從而實現資料通訊。

這樣我們在微信小程式中透過在邏輯層中 setData 來改變 Model 層的資料就能夠實現檢視資料的非同步更新。

關於小程式的一切,讀這一篇就夠了

以下是微信小程式的生命週期:

關於小程式的一切,讀這一篇就夠了

2.2 整體架構

關於小程式的一切,讀這一篇就夠了

注:以下所有內容均圍繞微信開發者工具展開。

開啟微信開發者工具的原始碼,他是基於 NW.js 執行的,所以下圖中的 package.nw 就是我們要重點鑽研的物件:

關於小程式的一切,讀這一篇就夠了

這裡面有很多程式碼,都是經過混淆與壓縮的,將程式碼在 VSCode 中開啟,並安裝 Prettier - Code formatter 外掛可以實現對原始碼的格式化。但此時程式碼中已經不具有語義化的變量了,只能透過 API 大致推斷程式碼是幹什麼的。

原始碼中有一個 vendor 資料夾是值得注意的,透過它可以快速新建一個示例專案,同時裡面有一個十分重要的 2.17.0.wxvpkg 包,它是微信小程式的基礎庫,包含了下文所提及的 WebService 與 WebView 等邏輯層與渲染層的處理。

關於小程式的一切,讀這一篇就夠了

2.2.1 WAWebview

小程式檢視層基礎庫,提供檢視層的基礎能力:

var __wxLibrary = {
  fileName: 'WAWebview.js',
  envType: 'WebView',
  contextType: 'others',
  execStart: Date.now()
};
var __WAWebviewStartTime__ = Date.now();
var __libVersionInfo__ = {
  "updateTime": "2020.4.4 10:25:02",
  "version": "2.10.4"
};

/**
 * core-js 模組
 */
!function(n, o, Ye) {
  ...
  }, function(e, t, i) {
    var n = i(3),
      o = "__core-js_shared__",
      r = n[o] || (n[o] = {});
    e.exports = function(e) {
      return r[e] || (r[e] = {})
    }
  ...
}(1, 1);

var __wxConfig;
var __wxTest__ = false;

var wxRunOnDebug = function(e) {
  e()
};

/**
 * 基礎模組
 */
var Foundation = function(i) {
  ...
}]).default;

var nativeTrans = function(e) {
  ...
}(this);

/**
 * 訊息通訊模組
 */
var WeixinJSBridge = function(e) {
  ...
}(this);

/**
 * 監聽 nativeTrans 相關事件
 */
!function() {
  ...
}();

/**
 * 解析配置
 */
!function(r) {
  ...
  __wxConfig = _(__wxConfig), __wxConfig = v(__wxConfig), Foundation.onConfigReady(function() {
    m()
  }), n ? __wxConfig.__readyHandler = A : d ? Foundation.onBridgeReady(function() {
    WeixinJSBridge.on("onWxConfigReady", A)
  }) : Foundation.onLibraryReady(A)
}(this);

/**
 * 異常捕獲(error、onunhandledrejection)
 */
!function(e) {
  function t(e) {
    Foundation.emit("unhandledRejection", e) || console.error("Uncaught (in promise)", e.reason)
  }
  "object" == typeof e && "function" == typeof e.addEventListener ? (e.addEventListener("unhandledrejection", function(e) {
    t({
      reason: e.reason,
      promise: e.promise
    }), e.preventDefault()
  }), e.addEventListener("error", function(e) {
    var t;
    t = e.error, Foundation.emit("error", t) || console.error("Uncaught", t), e.preventDefault()
  })) : void0 === e.onunhandledrejection && Object.defineProperty(e, "onunhandledrejection", {
    value: function(e) {
      t({
        reason: (e = e || {}).reason,
        promise: e.promise
      })
    }
  })
}(this);

/**
 * 原生緩衝區
 */
var NativeBuffer = function(e) {
  ...
}(this);
var WeixinNativeBuffer = NativeBuffer;
var NativeBuffer = null;

/**
 * 日誌模組:wxConsole、wxPerfConsole、wxNativeConsole、__webviewConsole__
 */
var wxConsole = ["log", "info", "warn", "error", "debug", "time", "timeEnd", "group", "groupEnd"].reduce(function(e, t) {
  return e[t] = function() {}, e
}, {});

var wxPerfConsole = ["log", "info", "warn", "error", "time", "timeEnd", "trace", "profile", "profileSync"].reduce(function(e, t) {
  return e[t] = function() {}, e
}, {});

var wxNativeConsole = function(i) {
  ...
}([function(e, t, i) {
  ...
}]).default;

var __webviewConsole__ = function(i) {
  ...
}([function(e, t, i) {
  ...
}]);

/**
 * 上報模組
 */
var Reporter = function(i) {
  ...
}([function(e, L, O) {
  ...
}]).default;

var Perf = function(i) {
  ...
}([function(e, t, i) {
  ...
}]).default;

/**
 * 檢視層 API
 */
var __webViewSDK__ = function(i) {
  ...
}([function(e, L, O) {
  ...
}]).default;
var wx = __webViewSDK__.wx;

/**
 * 元件系統
 */
var exparser = function(i) {
  ...
}([function(e, t, i) {
  ...
}]);

/**
 * 框架粘合層
 *
 * 使用 exparser.registerBehavior 和 exparser.registerElement 方法註冊內建元件
 * 轉發 window、wx 物件上到事件轉發到 exparser
 */
!function(i) {
  ...
}([function(e, t) {
  ...
}, function(e, t) {}, , function(e, t) {}]);

/**
 * Virtual DOM
 */
var __virtualDOMDataThread__ = false;
var __virtualDOM__ = function(i) {
  ...
}([function(e, t, i) {
  ...
}]);

/**
 * __webviewEngine__
 */
var __webviewEngine__ = function(i) {
  ...
}([function(e, t, i) {
  ...
}]);

/**
 * 注入預設樣式到頁面
 */
!function() {
  ...
  function e() {
     var e = i('...');
    __wxConfig.isReady ? void0 !== __wxConfig.theme && i(t, e.nextElementSibling) : __wxConfig.onReady(function() {
      void0 !== __wxConfig.theme && i(t, e.nextElementSibling)
    })
  }
  window.document && "complete" === window.document.readyState ? e() : window.onload = e
}();

var __WAWebviewEndTime__ = Date.now();
typeof __wxLibrary.onEnd === 'function' && __wxLibrary.onEnd();
__wxLibrary = undefined;

WAWebview 主要由以下幾個部分元件:

  • Foundation:基礎模組
  • WeixinJSBridge:訊息通訊模組
  • exparser:元件系統模組
  • __virtualDOM__:Virtual DOM 模組
  • __webViewSDK__:WebView SDK 模組
  • Reporter:日誌上報模組 (異常和效能統計資料)

2.2.2 WAService

小程式邏輯層基礎庫,提供邏輯層基礎能力:

var __wxLibrary = {
  fileName: 'WAService.js',
  envType: 'Service',
  contextType: 'App:Uncertain',
  execStart: Date.now()
};
var __WAServiceStartTime__ = Date.now();

(function(global) {
  var __exportGlobal__ = {};
  var __libVersionInfo__ = {
    "updateTime": "2020.4.4 10:25:02",
    "version": "2.10.4"
  };
  var __Function__ = global.Function;
  var Function = __Function__;

  /**
   * core-js 模組
   */
  !function(r, o, Ke) {
  }(1, 1);

  var __wxTest__ = false;
  var wxRunOnDebug = function(e) {
    e()
  };

  var __wxConfig;
  /**
   * 基礎模組
   */
  var Foundation = function(n) {
    ...
  }([function(e, t, n) {
    ...
  }]).default;

  var nativeTrans = function(e) {
    ...
  }(this);

  /**
   * 訊息通訊模組
   */
  var WeixinJSBridge = function(e) {
    ...
  }(this);

  /**
   * 監聽 nativeTrans 相關事件
   */
  !function() {
    ...
  }();

  /**
   * 解析配置
   */
  !function(i) {
    ...
  }(this);

  /**
   * 異常捕獲(error、onunhandledrejection)
   */
  !function(e) {
    ...
  }(this);

  /**
   * 原生緩衝區
   */
  var NativeBuffer = function(e) {
    ...
  }(this);
  WeixinNativeBuffer = NativeBuffer;
  NativeBuffer = null;

  var wxConsole = ["log", "info", "warn", "error", "debug", "time", "timeEnd", "group", "groupEnd"].reduce(function(e, t) {
      return e[t] = function() {}, e
    }, {});

  var wxPerfConsole = ["log", "info", "warn", "error", "time", "timeEnd", "trace", "profile", "profileSync"].reduce(function(e, t) {
    return e[t] = function() {}, e
  }, {});

  var wxNativeConsole = function(n) {
    ...
  }([function(e, t, n) {
    ...
  }]).default;

  /**
   * Worker 模組
   */
  var WeixinWorker = function(e) {
    ...
  }(this);

  /**
   * JSContext
   */
  var JSContext = function(n) {
    ...
  }([
    ...
  }]).default;

  var __appServiceConsole__ = function(n) {
    ...
  }([function(e, N, R) {
    ...
  }]).default;

  var Protect = function(n) {
    ...
  }([function(e, t, n) {
    ...
  }]);

  var Reporter = function(n) {
    ...
  }([function(e, N, R) {
    ...
  }]).default;

  var __subContextEngine__ = function(n) {
    ...
  }([function(e, t, n) {
    ...
  }]);

  var __waServiceInit__ = function() {
    ...
  }

  function __doWAServiceInit__() {
    var e;
    "undefined" != typeof wx && wx.version && (e = wx.version), __waServiceInit__(), e && "undefined" != typeof __exportGlobal__ && __exportGlobal__.wx && (__exportGlobal__.wx.version = e)
  }
  __subContextEngine__.isIsolateContext();
  __subContextEngine__.isIsolateContext() || __doWAServiceInit__();
  __subContextEngine__.initAppRelatedContexts(__exportGlobal__);
})(this);

var __WAServiceEndTime__ = Date.now();
typeof __wxLibrary.onEnd === 'function' && __wxLibrary.onEnd();
__wxLibrary = undefined;

WAService 基本組成:

  • Foundation:基礎模組
  • WeixinJSBridge:訊息通訊模組
  • WeixinNativeBuffer:原生 Buffer
  • WeixinWorker:Worker 執行緒
  • JSContext:JS Engine Context
  • Protect:JS 保護的物件
  • __subContextEngine__:提供 App、Page、Component、Behavior、getApp、getCurrentPages 等方法

2.2.3 虛擬 DOM

微信小程式在 WAService 裡面實現了小程式的 __virtualDOM__,透過 __virtualDOM__ 模組,可以實現 JS 物件到 DOM 物件的對映。

但是這個虛擬 DOM 透過 diff 和 patch 後並不是轉換成原生的 DOM 元素,而是微信小程式裡面自定義的 DOM 元素,這些 DOM 元素的操作透過 Exparser 模組來統一管理:

在 WAWebview 中包含了所有的 wx 自定義標籤:

關於小程式的一切,讀這一篇就夠了

同時,__virtualDOM__ 模組提供了很多的基礎 API,比如:

  • getAll:獲取所有 Node
  • getNodeById:根據 Id 獲取 Node
  • getNodeId:獲取 NodeId
  • addNode:新增節點
  • removeNode:刪除節點
  • getExparser:獲取 Exparser 物件(基於 WebComponent 的 shadow DOM 模型,可以在 JS 環境中執行,所有與節點樹相關的操作都依賴於他)
  • ...

(更多的 API 定義可以在 WAService.js 裡面去查詢)

2.2.4 WeiXinJSBridge

WeixinJSBridge 提供了檢視層 JS 與 Native、檢視層與邏輯層之間訊息通訊的機制,提供瞭如下幾個方法:

關於小程式的一切,讀這一篇就夠了

裡面最重要的便是 on 和 invoke,透過 on 來註冊事件,透過 invoke 來觸發相應的事件。

2.3 微信開發者工具

微信開發者工具中的小程式是跑在 NW.js 中的,這裡是他的官方 API 文件:https://nwjs.readthedocs.io/en/latest/

他是基於 Chromium 和 Node.js 的,因此我們編譯後的虛擬 DOM 轉換成真實 DOM 後,透過他來執行。

2.3.1 一些反編譯技巧

我們可以透過開發者工具,在 Devtools 裡輸入 help 可以得到很多指令:

關於小程式的一切,讀這一篇就夠了

其中比較有用的是 openVendor。這個函式可以開啟當前專案的原始碼,其實也就是包含了 wcc 和 wcsc 編譯工具的一個資料夾:

有了這些檔案之後,對我們之後的分析會很有幫助。

我們可以將這些檔案複製到一個單獨的目錄,在 VSCode 中開啟該專案,並安裝以下外掛:

關於小程式的一切,讀這一篇就夠了

這個外掛可以將微信開發者工具中的所有以 .wxvpkg 結尾的檔案進行解壓縮。

同時透過他來將 quickstart 中的 miniProgramJs.wxvpkg 進行解壓,得到我們在開發者工具中的原始碼檔案。

2.3.2 編譯原理

2.3.2.1 wcc 編譯 wxml

微信小程式提供了 wcc 工具來編譯 wxml 程式碼。透過上面得到的程式碼,我們可以實現對 wxml 的編譯,以開發者工具建立的 Demo 專案中的首頁為例:

<view class="container">
  <view class="userinfo">
    <block wx:if="{{canIUseOpenData}}">
      <view class="userinfo-avatar" bindtap="bindViewTap">
        <open-data type="userAvatarUrl"></open-data>
      </view>
      <open-data type="userNickName"></open-data>
    </block>
    <block wx:elif="{{!hasUserInfo}}">
      <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 獲取頭像暱稱 </button>
      <button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 獲取頭像暱稱 </button>
      <view wx:else> 請使用1.4.4及以上版本基礎庫 </view>
    </block>
    <block wx:else>
      <image bindtap="bindViewTap" class="userinfo-avatar" class="lazyload" data-src="{{userInfo.avatarUrl}}" src="/assets/loading.gif" mode="cover"></image>
      <text class="userinfo-nickname">{{userInfo.nickName}}</text>
    </block>
  </view>
  <view class="usermotto">
    <text class="user-motto">{{motto}}</text>
  </view>
</view>

經過以下指令編譯:

./wcc ./quickstart/miniProgramJs.unpack/pages/index/index.wxml > index.js

會得到 JS 描述檔案:

關於小程式的一切,讀這一篇就夠了

它會宣告一個 $gwx 函式,透過它可以得到 Virtual DOM。接著我們在這個檔案裡新增幾行程式碼去呼叫它,並透過 Node.js 或者 NW.js 執行這個檔案:

var data = $gwx('./quickstart/miniProgramJs.unpack/pages/index/index.wxml')();
console.log(JSON.stringify(data, null, 2));

可以得到我們想要的最終的 Virtual DOM 結構:

{
  "tag": "wx-page",
  "children": [
    {
      "tag": "wx-view",
      "attr": {
        "class": "container"
      },
      "children": [
        {
          "tag": "wx-view",
          "attr": {
            "class": "userinfo"
          },
          "children": [
            {
              "tag": "wx-view",
              "attr": {},
              "children": [
                " 請使用1.4.4及以上版本基礎庫 "
              ],
              "raw": {},
              "generics": {}
            }
          ],
          "raw": {},
          "generics": {}
        },
        {
          "tag": "wx-view",
          "attr": {
            "class": "usermotto"
          },
          "children": [
            {
              "tag": "wx-text",
              "attr": {
                "class": "user-motto"
              },
              "children": [
                ""
              ],
              "raw": {},
              "generics": {}
            }
          ],
          "raw": {},
          "generics": {}
        }
      ],
      "raw": {},
      "generics": {}
    }
  ]
}

然後透過 window.exparser.registerElemtent 方法將這些 tag 轉換成真實 DOM:

關於小程式的一切,讀這一篇就夠了

比如說,以上的 wx-text 就會被轉換成類似於以下 DOM:

<span>
   <span style="display: none"></span>
   <span>{{這裡是具體的文字內容}}</span>
</span>

2.3.2.2 wcsc 編譯 wxss

同樣的,以 Demo 專案裡的 index.wxss 為例,執行一下指令:

./wcsc ./quickstart/miniProgramJs.unpack/pages/index/index.wxss -js -o ./css.js

可以將該檔案從 wxss 格式的內容,轉換成 JS 的內容:

關於小程式的一切,讀這一篇就夠了

這裡面會生成 setCssToHead 方法,用於將相應的 css 轉換後(如 rpx 轉 px 等等),透過 style 標籤插入到文件的 head 裡面。

2.4 通訊原理

小程式邏輯層和渲染層的通訊會由 Native (微信客戶端)做中轉,邏輯層傳送網路請求也經由 Native 轉發。

檢視層元件

內建元件中有部分元件是利用到客戶端原生提供的能力,既然需要客戶端原生提供的能力,那就會涉及到檢視層與客戶端的互動通訊。這層通訊機制在 iOS 和安卓系統的實現方式並不一樣,iOS 是利用了 WKWebView 的提供 messageHandlers 特性,而在安卓則是往 WebView 的 window 物件注入一個原生方法,最終會封裝成 WeiXinJSBridge 這樣一個相容層,主要提供了呼叫(invoke)和監聽(on)這兩種方法。

邏輯層介面

邏輯層與客戶端原生通訊機制與渲染層類似,不同在於,iOS 平臺可以往 JavaScriptCore 框架注入一個全域性的原生方法,而安卓方面則是跟渲染層一致的。

無論是檢視層還是邏輯層,開發者都是間接地呼叫到與客戶端原生通訊的底層介面。一般微信小程式會對邏輯層介面做層封裝後,才暴露給開發者,封裝的細節可能是統一入參、做些引數校驗、相容各平臺或版本問題等等。

2.5 啟動機制

小程式有冷啟動與熱啟動兩種方式:

  • 假如使用者已經開啟過某小程式,然後在一定時間內再次開啟該小程式,此時無需重新啟動,只需將後臺態的小程式切換到前臺,這個過程就是熱啟動。
  • 冷啟動指的是使用者首次開啟或小程式被微信主動銷燬後再次開啟的情況,此時小程式需要重新載入啟動。

小程式沒有重啟的概念:

  • 當小程式進入後臺,客戶端會維持一段時間的執行狀態,超過一定時間後(目前是 5 分鐘)會被微信主動銷燬。
  • 當短時間內(5s)連續收到兩次以上收到系統記憶體告警,會進行小程式的銷燬。

啟動流程:

關於小程式的一切,讀這一篇就夠了

3. 總結

  • 小程式擁有接近原生 App 的體驗。
  • 小程式並不是真正的 “無需下載”,只是小程式的體積很小,在當今高速的網路環境下能夠快速下載,使用者感知不到,更確切的來說是 “無感下載”
  • 基於移動端佈局的侷限性,可以高效且簡單的開發,迭代快速。
  • 小程式是雙執行緒模型,邏輯層和渲染層分別執行在不同的執行緒中,透過 JSBridge 進行通訊。
  • 在小程式裡有實現專門的 JSBridge 來實現 JS 和 Native 的雙向呼叫。
  • wxml 檔案透過 wcc 編譯:wxml => JS => VirtualDOM。
  • wxss 檔案透過 wcsc 編譯:wxss => JS => style。
  • 小程式其實也是一種 hybrid 技術,但是他圍繞宿主應用,實現了更為強大的生態,提供更為便捷的服務。

4. 參考文獻

  • 微信小程式技術原理分析(https://zhaomenghuan.js.org/blog/wechat-miniprogram-principle-analysis.html#%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B)
  • 淺談小程式的歷史發展和現狀以及一些多端解決方案(https://juejin.cn/post/6948602456003575821)
  • 阿拉丁研究院 - 網際網路行業:2020 年小程式網際網路發展白皮書(https://pdf.dfcfw.com/pdf/H3_AP202101151450898869_1.pdf?1610717870000.pdf)
  • 一篇文章瞭解 JsBridge(https://juejin.cn/post/6844903567560540173)

5.原文地址

https://mp.weixin.qq.com/s/5nQqBFFWwxtcf8S2Ba9PRA

分類: 房產
時間: 2021-11-18

相關文章

農村四大怪象,“地閒著、房空著、車停著、錢存著”,咋回事?

農村四大怪象,“地閒著、房空著、車停著、錢存著”,咋回事?
導語:隨著社會經濟的發展,人們的物質文化生活水平都在不斷提高,農村也是如此.現在的農村跟以前相比,可以說是發生了翻天覆地的變化.以前種地時,都是村民面朝黃土背朝天的田間勞作形象,而如今一到農忙時節,地 ...

來了!帝都最有潛力的地方在這裡!72平小戶型準臨鐵房,真香?

來了!帝都最有潛力的地方在這裡!72平小戶型準臨鐵房,真香?
哈嘍大家好,我是血拼哥. 前段時間給大家盤了一下大朝陽今年"砸盤"式入市的前因後果,戳這裡複習,有朋友後臺留言希望哥們展開說說. 恰巧上週四北京市政府新聞辦召開了"北京推 ...

9.24上海房車展:鐵漢子的真柔情,飛翔-鐵C無拓展房車

9.24上海房車展:鐵漢子的真柔情,飛翔-鐵C無拓展房車
繼鐵C-新概念後,飛翔房車重拳出擊,再度推出鐵C無拓展B型房車,鐵殼車身,黃金比例的車身,充足的內部空間,誰說B型就要將就?鐵C從不妥協,靈活.舒適全部擁有! 首先來看外觀,鐵C無拓展的車身外尺寸為5 ...

激動的心,顫抖的手!正商杏海苑選房結束!基本售罄,還有兩套

激動的心,顫抖的手!正商杏海苑選房結束!基本售罄,還有兩套
幫找鄰居,購買(正商杏海苑)的粉絲可以私信我,備註:正商杏海苑業主,房家粉絲福利,不定期踩盤更新小區施工進度,公益收房驗房等福利,踩盤不易,歡迎大家點贊分享轉發. 正商杏海苑 2021年大興區正商杏海 ...

距望京直線距離約3公里,價格卻差百萬+崔各莊板塊的車能上嗎

距望京直線距離約3公里,價格卻差百萬+崔各莊板塊的車能上嗎
崔各莊限價8萬8值嗎? 說到崔各莊,咱就要先說說8萬8的價格值不值得入手. 從目前的情況來看,望京有品質的小區,二手掛牌價格基本都是在10w+以上,比如臻園. 而崔各莊距望京直線距離也就3公里左右,如 ...

普京真夠意思!不得不點個大大的贊

普京真夠意思!不得不點個大大的贊
不得不說,中俄關系是真的鐵,普京也真夠意思! 現在是2021年9月,他就高調接受中方邀請,決定出席冬奧會開幕式了.據俄羅斯衛星社網站9月16日報道,俄羅斯外長拉夫羅夫稱,俄羅斯總統普京接受中方邀請,將 ...

京系房企發展現狀及擴張模式探究

京系房企發展現狀及擴張模式探究
作為首都,北京是全國的政治中心.文化中心.國際交往中心和科技創新中心,人均GDP居全國首位,房地產開發投資方面,由於受到嚴格的調控以及首都城市的戰略定位,在2015年開發投資總額觸及波峰之後出現了連續 ...

尾盤房爭議大,買房自住究竟值不值得選購?看看這6點再決定

尾盤房爭議大,買房自住究竟值不值得選購?看看這6點再決定
房地產住宅專案銷售率達到70%左右時,我們會把剩餘未銷售出去的房子統一稱為尾盤房,不過很多時候,人們都覺著尾盤房是別人挑剩下的房子,自己也就不願意挑選,不過尾盤房也是和其他房子同期建設起來的,所以質量 ...

車企開始“退房”了
樂居財經 陳晨 發自十堰 繼三年前轉讓東風汽車房地產有限公司(簡稱"東風汽車房地產")80%股權之後,眼下,東風汽車將剩餘的20%股權也轉讓予招商蛇口.至此,東風汽車徹底退出了房地 ...

豐臺史話(56)| 京西南的創新發展:鐵路與豐臺鎮的不解之緣(一)
為迎接中國共產黨百年華誕, 豐臺區融媒體中心 與豐臺區委黨史工作辦公室 共同推出有聲欄目<豐臺史話>, 每週為您講述 發生在豐臺的紅色故事-- 豐臺史話 京西南的創新發展 ​ 本期< ...

剛需妥協的新姿勢!500萬的東西向兩居,你還買嗎?

剛需妥協的新姿勢!500萬的東西向兩居,你還買嗎?
這幾年,西邊買房人是幸福的,網紅大盤很多,讓人挑花了眼. 但西邊的剛需是不幸的,手拿500萬真的無房可選. 石景山已經邁入8萬,而豐臺早就豪宅化. 今年上新的豐臺14號線大瓦窯站的天閱山河和國譽萬和城 ...

北京樓市:200萬首付選這裡,穩賺不賠

北京樓市:200萬首付選這裡,穩賺不賠
我是北京房姐,資深房產投資專家.目前已為10000+人提供買房最佳解決方案.房姐不像其他自媒體遮遮掩掩的讓你摸不清頭腦!房姐屬於實戰派只說對你最有用的操作和建議. 以下內容選自[北京房姐]公眾號| 粉 ...

北京樓市:記住!能買次新,不買老破小
我是北京房姐,資深房產投資專家.目前已為10000+人提供買房最佳解決方案.房姐不像其他自媒體遮遮掩掩的讓你摸不清頭腦!房姐屬於實戰派只說對你最有用的操作和建議. 以下內容選自[北京房姐]公眾號| 粉 ...

共有產權房挺進三環!個人產權80%起

共有產權房挺進三環!個人產權80%起
哈嘍大家好,我是血拼哥. 前天大夜裡規自委掛出了今年帝都第二次集中供地的地塊資訊,昨天就已經有房屋銷售指導價傳了出來,據說是開發商們從住建委官網申請查詢的. 精美大套表放在文末了,需要的朋友們自取. ...

北京3盤上新 1859套房源批次入市

北京3盤上新 1859套房源批次入市
樂居買房訊(壹壹)9月13日北京有3個專案領取預售證,共計批准入市1859套房源,專案分別為保利•錦上(悅錦雅苑).中建京西印玥(興辰佳苑).北京建工·璟玥林汐(璟玥雅苑). ①保利•錦上 保利•錦上 ...

盤點2021年北京周邊樓盤的“價格”與“位置優勢”到底該如何入手

盤點2021年北京周邊樓盤的“價格”與“位置優勢”到底該如何入手
從2020年--2021年,我們承受了很多,疫情以及洪水.這兩年的經歷也是多災多難啊,而2021的房價如何了呢?相信很多沒有買房的小夥伴也在一直觀望和在網上搜索瞭解哪裡的房子最便宜而物美價廉. 今天為 ...

北京樓市:這個板塊常年跑輸大盤,買入就被套

北京樓市:這個板塊常年跑輸大盤,買入就被套
我是北京房姐,資深房產投資專家.目前已為10000+人提供買房最佳解決方案.房姐不像其他自媒體遮遮掩掩的讓你摸不清頭腦!房姐屬於實戰派只說對你最有用的操作和建議. 以下內容選自[北京房姐]公眾號| 粉 ...

清華園車站舊址成新晉“紅色傳承”打卡地!

清華園車站舊址成新晉“紅色傳承”打卡地!
在海淀中關村街道科馨社群內 有一座老站房 熙熙攘攘.人來人往之間 這座老站房見證著歷史的變遷 詹天佑親手題寫的 "清華園車站"仍然清晰可見 這座老站房就是距今已有 111年曆史的清 ...

民間故事:乾隆施巧計,妙傳功臣
清朝年間,乾隆皇帝為了褒獎本朝對社稷有功之臣,命宮廷畫師為功臣們畫像,送進紫光閣內懸掛,供人瞻仰.從年初到入夏,乾隆已挑出二百八十位官員進入紫光閣,其中他的寵臣和更是名列前茅. 這天,乾隆站在紫光閣中 ...

他收藏了萬餘種老報紙,看看明代“邸報”長什麼樣?

他收藏了萬餘種老報紙,看看明代“邸報”長什麼樣?
在集報圈裡,提起範光永,那可是響噹噹的人物,不僅北京的集報人熟悉他,就是在外地的集報圈,也都知道他.他從事老報刊收藏已經有三十多年,主要收集明清.民國老報刊,還收集現代報紙創刊號.號外等,共收藏報紙計 ...